天天看點

PHP代碼審計總結審計思路sql注入靶場分析

PHP代碼審計

  • 審計思路
  • sql注入
    • 注入方式
      • 1. 普通注入
        • 概述
        • 資料庫操作
        • 資料庫查詢
      • 2. 寬位元組注入
        • 概述
        • addslashes()函數
        • 繞過方法
      • 3. 編碼注入
        • 概述
        • urlCode二次編碼分析
  • 靶場分析
    • 函數繞過
      • extract方法變量覆寫。
      • ereg正則%00繞過(科學計數法)
      • ereg正則%00繞過(strpos函數)
    • 字元型繞過orSQL注入繞過
      • 繞過過濾的空白字元
      • sql注入繞過
      • MD5SQL注入繞過
      • SQL閉合繞過
      • SQL注入or繞過
      • md5函數true繞過注入
    • 加密繞過
      • 檔案加密
      • MD5相似繞過
      • 密碼MD5比較繞過
      • 18 md5()函數===使用數組繞過
      • 十六進制的比較
      • ereg_replace的\e參數漏洞
    • 危險函數示例
      • strcmp危險函數
      • sha()危險函數
      • intval取整函數繞過
      • 19 ereg()函數strpos() 函數用數組傳回NULL繞過
      • 弱類型整數大小比較繞過(is_numeric)
      • md5函數驗證繞過
    • 頭部驗證繞過
      • session驗證繞過
      • X_FORWARDED_FOR頭部繞過
    • 其他
      • php://input和substr繞過。

審計思路

web安全重點就三個詞——“輸入”,“輸出”,“資料流”。

1、逆向追蹤/回溯變量—檢查敏感函數的參數,判斷參數可控?是否有過濾?

搜尋響應的敏感關鍵字,定向挖掘,高效、高品質
           

2、正向追蹤/跟蹤變量—找到哪些檔案在接收外部傳入的參數,跟蹤變量的傳遞過程,觀察是否有高危函數

3、直接挖掘功能點漏洞—根據自身經驗判斷

4、通讀全文—index檔案,了解程式架構

函數集檔案,通常名為functions,common 等

                  配置檔案,config

                  安全過濾檔案,filter safe,check
           

sql注入

注入方式

1. 普通注入

概述

普通注入分為字元型注入和數值型注入

資料庫操作

select from
mysql_connect
mysql_query
mysql_fetch_row
           

資料庫查詢

update
insert
delete
           

2. 寬位元組注入

概述

1、單位元組字元集:所有的字元都使用一個位元組來表示,比如 ASCII 編碼(0-127)

2、多位元組字元集:在多位元組字元集中,一部分位元組用多個位元組來表示,另一部分(可能沒有)用單個位元組來表示。

3、寬位元組注入時利用mysql的一個特性,使用GBK編碼的時候,會認為兩個字元是一個漢字

addslashes()函數

1、addslashes() 函數傳回在預定義字元之前添加反斜杠的字元串。

2、預定義字元:單引号(’),雙引号("),反斜杠(\),NULL

3、 示例

<?php
$ss=addslashes('aiyou"bu"cuoo');
echo($ss);
?>
運作結果:
aiyou\"bu\"cuoo
           

繞過方法

我們可以在輸入的時候對于特殊符号引号,斜杠,反斜杠這些在他前面加%df使得讓他和“\”這個符号在資料庫中進行組合編譯,編譯成一個漢字,以至于繞過寬位元組。

這個代碼就是寬位元組繞過之後資料庫查詢的代碼

3. 編碼注入

概述

通過輸入轉碼函數不相容的特殊字元,可以導緻輸出的字元程式設計有害資料。最常見的編碼注入就是Mysql寬位元組以及urldecode/rawurldecode。urldecode是一個轉碼函數,rawurldecode是一個解碼函數。

urlCode二次編碼分析

還是先上源碼

<?php
if(eregi("hackerDJ",$_GET[id])) {
  echo("<p>not allowed!</p>");
  exit();
}

$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ")
{
  echo "<p>Access granted!</p>";
  echo "<p>flag: *****************} </p>";
}
?>
           

這裡我們要了解一點課外知識了

eregi — 不區分大小寫的正規表達式比對

我們需要繞過第一個判斷,也就是id的值不等于hackerDJ,但是第二個判斷有需要id的值等于它。是以這個肯定是有漏洞的。在這一行進行了url編碼的解碼

是以我們要進行二次編碼,将hackerDJ值進行編碼,然後繞過第一個比對到第二個。

hackerDJ的url編碼:%68%61%63%6b%65%72%44%4a

hackerDJ的二次編碼為:%25%36%38%25%36%31%25%36%33%25%36%62%25%36%35%25%37%32%25%34%34%25%34%61

結果

PHP代碼審計總結審計思路sql注入靶場分析

靶場分析

函數繞過

extract方法變量覆寫。

<?php

$flag='xxx'; 
extract($_GET);
 if(isset($shiyan))
 { 
    $content=file_get_contents($flag);
    if($shiyan==$content)
    { 
        echo'ctf{xxx}'; 
    }
   else
   { 
    echo'Oh.no';
   } 
   }

?>
           

可以看到漏洞點就在我标注的那兩個地方。

PHP代碼審計總結審計思路sql注入靶場分析

第一個GET值沒有說明傳遞的具體值,是以我們可以都可以傳遞這個檔案裡的所有值。

第二個content的值就是傳遞的flag檔案内容。

因為這裡判斷了content參數和shiyan這個參數相等則會擷取到flag值

PHP代碼審計總結審計思路sql注入靶場分析

是以我們隻需要構造flag參數的檔案内容和shiyan參數的值相同就可以。但是flag值已經給了。我們又可以利用extract函數将flag參數值給覆寫掉。

但是我們不知道檔案裡面的内容。是以我們可以利用file_get_contents函數的漏洞,當檔案不存在則會傳回空值。

是以我們構造payload

PHP代碼審計總結審計思路sql注入靶場分析

ereg正則%00繞過(科學計數法)

還是先分析源代碼吧

<?php 

$flag = "flag";

if (isset ($_GET['password'])) 
{
  if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
  {
    echo '<p>You password must be alphanumeric</p>';
  }
  else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
   {
     if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字元串首次出現的位置
      {
      die('Flag: ' . $flag);
      }
      else
      {
        echo('<p>*-* have not been found</p>'); 
       }
      }
     else 
     {
        echo '<p>Invalid password</p>'; 
      }
   } 
?>
           

先分析這三個判斷吧。

if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
  {
    echo '<p>You password must be alphanumeric</p>';
  }
           

這個判斷就是說隻能以a-z,A-Z,0-9開頭并結尾。

這個随便輸入數字字母都能繞過

PHP代碼審計總結審計思路sql注入靶場分析

然後這個判斷判斷的是長度小于8,然後數值要大于9999999

else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
   {
           

這題我們隻能利用科學計數法繞過

payload:12e12

PHP代碼審計總結審計思路sql注入靶場分析

這個判斷我們需要借用0x00截斷進行攻擊了。

将後面的特殊符号給截斷。

payload:1e9%00*-*

PHP代碼審計總結審計思路sql注入靶場分析

ereg正則%00繞過(strpos函數)

<?php

$flag = "flag";

    if (isset ($_GET['nctf'])) {
        if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
            echo '必須輸入數字才行';
        else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)   
            die('Flag: '.$flag);
        else
            echo '騷年,繼續努力吧啊~';
    }

 ?>
           

這段代碼也沒啥說的,他必須是數字,但是他又要包含字元串。是以第一印象應該采用%00進行截斷。

PHP代碼審計總結審計思路sql注入靶場分析

但是還是沒有繞過第二個判斷,必須包含字元串。是以嘗試将#号修改為%23。

成功

PHP代碼審計總結審計思路sql注入靶場分析

這題應該關鍵點是考的是oxoo截斷吧。

字元型繞過orSQL注入繞過

繞過過濾的空白字元

<?php
 
$info = ""; 
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 
ini_set("display_error", false); //為一個配置選項設定值
error_reporting(0); //關閉所有PHP錯誤報告
 
if(!isset($_GET['number'])){
   header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP頭顯示hint 26966dc52e85af40f59b4fe73d8c323a.txt
 
   die("have a fun!!"); //die — 等同于 exit()
 
}
           

這段代碼的意思是傳遞需要一個number值,他會判斷如果number值不存在則在網頁的頭部添加hint請求頭之後退出。

foreach([$_GET, $_POST] as $global_var) {  //foreach 文法結構提供了周遊數組的簡單方式 
    foreach($global_var as $key => $value) { 
        $value = trim($value);  //trim — 去除字元串首尾處的空白字元(或者其他字元)
        is_string($value) && $req[$key] = addslashes($value); // is_string — 檢測變量是否是字元串,addslashes — 使用反斜線引用字元串
    } 
} 
           

這段代碼就是将傳遞過來的number值和POST傳遞過來的值進行組合,将他們組合成數組。然後周遊這個數組,去掉value值空字元和其他字元。

然後将value值添加到前面建立的清單中。

function is_palindrome_number($number) { 
    $number = strval($number); //strval — 擷取變量的字元串值
    $i = 0; 
    $j = strlen($number) - 1; //strlen — 擷取字元串長度
    while($i < $j) { 
        if($number[$i] !== $number[$j]) { 
            return false; 
        } 
        $i++; 
        $j--; 
    } 
    return true; 
} 
           

這段代碼雖然他寫了一個循環,但是隻運作了一次。就是判斷number這個字元串的首尾相不相同,如果相同則傳回true,否則就傳回false。

if(is_numeric($_REQUEST['number'])) //is_numeric — 檢測變量是否為數字或數字字元串 
{
 
   $info="sorry, you cann't input a number!";
 
}
elseif($req['number']!=strval(intval($req['number']))) //intval — 擷取變量的整數值
{
 
     $info = "number must be equal to it's integer!! ";  
 
}
           

這段代碼就是判斷輸入的number值是否為數字。

else
{
 
     $value1 = intval($req["number"]);
     $value2 = intval(strrev($req["number"]));  
 
     if($value1!=$value2){
          $info="no, this is not a palindrome number!";
     }
     else
     {
 
          if(is_palindrome_number($req["number"])){
              $info = "nice! {$value1} is a palindrome number!"; 
          }
          else
          {
             $info=$flag;
          }
     }
 
}
 
echo $info;
           

定義兩個變量,一個正向取number值,一個逆向取number值。然後判斷首尾是否相同。下面在判斷輸入的number值是否為數字,如果是則将他是數字這個字元串傳遞給info。反之則将flag值傳遞給info然後輸出。

是以我們要構造number值為字元串,但是又要繞過前面的判斷他首尾相同。是以我們構造一個首尾相同并且又是字元串的數值,還要等于它本身。

payload=?number=%00%2B121

因為他判斷了進行了四次判斷。我們都要繞過。

1. is_numeric($_REQUEST['number']		      ## 判斷是否為數字
2. $req['number']!=strval(intval($req['number']))			##判斷req清單中的number的字元串是否和取出的數值轉換後的字元串相等
3. intval($req["number"]) == intval(strrev($req["number"]))		##判斷首尾是否相同
4. is_palindrome_number($req["number"]		##這個函數也是否首尾相同
           

第一個我們要繞過他讓他變為假。是以我們可以使用0x00進行截斷。

第二個我們可以不用繞過

第三個與第二個一樣把他轉換成為數值型比較。也不用繞過

第四個比較首尾相不相同,我們要繞過他使他不相同,我們在他數值前面或者後面添加%2B也就是“+”号,讓他繞過。

PHP代碼審計總結審計思路sql注入靶場分析

sql注入繞過

直接上源代碼

function AttackFilter($StrKey,$StrValue,$ArrReq){  
    if (is_array($StrValue)){

//檢測變量是否是數組

        $StrValue=implode($StrValue);

//傳回由數組元素組合成的字元串

    }
    if (preg_match("/".$ArrReq."/is",$StrValue)==1){   

//比對成功一次後就會停止比對

        print "水可載舟,亦可賽艇!";
        exit();
    }
}
           
PHP代碼審計總結審計思路sql注入靶場分析

這個函數定義的是将傳遞過去的post參數變為一個字元串

PHP代碼審計總結審計思路sql注入靶場分析

上面代碼又将post傳輸過去的字元串進行過濾将一些關鍵字等特殊符号進行過濾掉。

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
    die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);

//設定活動的 MySQL 資料庫
           

這段代碼毫無作用,可以将他注釋掉。

if($key['pwd'] == $_POST['pwd']) {
        print "CTF{XXXXXX}";
    }else{
        print "亦可賽艇!";
    }
}else{
    print "一顆賽艇!";
}
           

這段代碼就是當查詢到的記錄和Post表單傳遞過去的參數相同就輸出flag。反之則退出。

是以說關鍵點在輸入的使用者名和密碼這裡,。

PHP代碼審計總結審計思路sql注入靶場分析

我們可以繞過這裡進行注入,由源代碼可以看到是單引号注入。

但是因為他過濾了很多條件。

是以我們不能注入,但是我們可以構造查詢語句繞過下面的判斷。

PHP代碼審計總結審計思路sql注入靶場分析

我們可以在uname上構造:1’ group by password with roolup limit 1 offset 1

讓他的結果查詢的password的結果為空,。

完整代碼示範。

輸入1就會查詢使用者名為1的記錄

PHP代碼審計總結審計思路sql注入靶場分析

現在來補充一點小知識

limit 1		表示取一條記錄
limit 1,2	表示從第二條開始取兩個
offset 1	表示從第二條記錄開始
           

現在回歸正題,我們可以建構上面的代碼,讓他的password給替換成為空,這樣我們就拿到了他的flag值。

PHP代碼審計總結審計思路sql注入靶場分析

這題也可以建構**1’ group by password with limit 1,1 – **。

MD5SQL注入繞過

先上源代碼

<?php

//配置資料庫
// if($_POST[user] && $_POST[pass]) {
//     $conn = mysql_connect("********, "*****", "********");
//     mysql_select_db("phpformysql") or die("Could not select database");
//     if ($conn->connect_error) {
//         die("Connection failed: " . mysql_error($conn));
// } 

//指派

$user = $_GET[user];
$pass = md5($_GET[pass]);

//sql語句

// select pw from php where user='' union select 'e10adc3949ba59abbe56e057f20f883e' # 

// ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456

$sql = "select pw from php where user='$user'";
// $query = mysql_query($sql);
// if (!$query) {
//     printf("Error: %s\n", mysql_error($conn));
//     exit();
// }
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];

  if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {

//如果 str1 小于 str2 傳回 < 0; 如果 str1 大于 str2 傳回 > 0;如果兩者相等,傳回 0。


    echo "<p>Logged in! Key:************** </p>";
}
else {
    echo("<p>Log in failure!</p>");

  }
//}
?>
           

别看源代碼如此之多,其實就幾條關鍵語句

<?php

$user = $_GET[user];
$pass = md5($_GET[pass]);

$sql = "select pw from php where user='$user'";// $query = mysql_query($sql);
$row = mysql_fetch_array($query, MYSQL_ASSOC);

  if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {

//如果 str1 小于 str2 傳回 < 0; 如果 str1 大于 str2 傳回 > 0;如果兩者相等,傳回 0。

    echo "<p>Logged in! Key:************** </p>";
}
else {
    echo("<p>Log in failure!</p>");
  }

?>
           

就這幾段代碼有用,我們先分析吧。

看道源代碼發現了一個sql單引号字元注入漏洞。

然後看到輸入的pass經過了md5加密。

我們看到這下面這一段代碼,既要讓sql中查詢到内容還要讓pass和pw也就是資料庫查詢的值相等,是以我們要構造sql注入md5值進行繞過,使得pw有值才能擷取到flag值。

是以我們可以利用md5進行加密與構造的pass值相同。

PHP代碼審計總結審計思路sql注入靶場分析

然後我們構造payload

PHP代碼審計總結審計思路sql注入靶場分析

是以這裡關鍵點是構造的sql函數,隻要函數構造對了就能成功。

SQL閉合繞過

<?php

$user = $_GET[user];
$pass = md5($_GET[pass]);


$sql = "select user from php where (user='$user') and (pw='$pass')";

$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
  if($row['user']=="admin") {
    echo "<p>Logged in! Key: *********** </p>";
  }

  if($row['user'] != "admin") {
    echo("<p>You are not admin!</p>");
  }
}

?>
           

這個都看了無數遍了,沒多少難的,一眼就看到了sql注入漏洞。多參數單引号右括号閉合。

因為這裡隻是對user進行了判斷是否為admin,沒有對pass這個進行判斷,是以我們可以在user哪裡進行sql注入,不用管pass字段的值直接在構造的sql注入後面進行注釋。

PHP代碼審計總結審計思路sql注入靶場分析

然後一直報錯,甚至我輸出他的user和pass值都有

PHP代碼審計總結審計思路sql注入靶場分析

最後檢視源碼才恍然大悟,是我根本沒有這個資料庫裡面的表,傻了!!!

PHP代碼審計總結審計思路sql注入靶場分析

這個表沒有肯定查詢出來的結果也沒有。是以Row值是空的,是以flag值出現不了。

SQL注入or繞過

源代碼

<?php

#GOAL: login as admin,then get the flag;
error_reporting(0);
require 'db.inc.php';

function clean($str){
    if(get_magic_quotes_gpc()){ //get_magic_quotes_gpc — 擷取目前 magic_quotes_gpc 的配置選項設定
        $str=stripslashes($str); //傳回一個去除轉義反斜線後的字元串(\' 轉換為 ' 等等)。雙反斜線(\\)被轉換為單個反斜線(\)。 
    }
    return htmlentities($str, ENT_QUOTES);
}
$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);
//$query='SELECT * FROM users WHERE name=\''admin\'\' AND pass=\''or 1 #'\';';
$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
    die('Invalid password!');
}

echo $flag;

?>

           

上面代碼其實clear基本沒用,有用的是下面的代碼。

$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
    die('Invalid password!');
}

echo $flag;
           

這段代碼說的是查詢有結果,則傳回flag值。

PHP代碼審計總結審計思路sql注入靶場分析

在這一題一直嘗試,結果發現我沒有那個資料庫。

md5函數true繞過注入

<?php 
$link = mysql_connect('127.0.0.1:3306', 'root', 'root');
if (!$link) { 
  die('Could not connect to MySQL: ' . mysql_error()); 
} 
// 選擇資料庫
$db = mysql_select_db("security", $link);
if(!$db)
{
  echo 'select db error';
  exit();
}
// 執行sql
$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
var_dump($sql);
$result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );
$row1 = mysql_fetch_row($result);
var_dump($row1);
mysql_close($link);
?>
           

這段代碼太長了,還是看關鍵的。

就這一段,因為你要繞過他,使他有答案,是以我們隻能看答案了。

死記硬背吧。

PHP代碼審計總結審計思路sql注入靶場分析

因為“ffifdyop”的十六位二進制數加密之後是

'or'6

剛好繞過sql注入。是以答flag就直接出來了。

PHP代碼審計總結審計思路sql注入靶場分析

這題沒有任何捷徑,隻能死記硬背

ffifdyop

加密繞過

檔案加密

PHP代碼審計總結審計思路sql注入靶場分析

開始打開網頁以為伺服器崩了。一直不顯示正确的網頁,後面看到源碼才發現自己還是太年輕了。

最重要的代碼在下方。

if(isset($requset['token']))
    //測試變量是否已經配置。若變量已存在則傳回 true 值。其它情形傳回 false 值。
    {
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
        //gzuncompress:進行字元串壓縮
        //unserialize: 将已序列化的字元串還原回 PHP 的值

        $db = new db();
        $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
        //mysql_real_escape_string() 函數轉義 SQL 語句中使用的字元串中的特殊字元。

        if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        }else if($row['pass'] !== $login['pass']){
            echo 'unserialize injection!!';
        }else{
            echo "(╯‵□′)╯︵┴─┴ ";
        }
    }else{
        header('Location: index.php?error=1');
    }
           
PHP代碼審計總結審計思路sql注入靶場分析

一進去就判斷token值,如果他存在則執行下面操作。不存在就重定向到報錯頁面。搞得我以為伺服器炸了。

在看if語句裡的關鍵内容吧。

這段代碼就是将token值進行base64編碼,編碼之後讓他進行壓縮,更氣的是壓縮之後還要序列化它。

if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        }else if($row['pass'] !== $login['pass']){
            echo 'unserialize injection!!';
        }else{
            echo "(╯‵□′)╯︵┴─┴ ";
        }
           

這裡判斷login數組的user值為ichunqiu,也就是上面token值壓縮之後的值為ichunqiu。

是以我們要先将token值進行反序列化和解壓縮。也就是構造ichunqiu。

PHP代碼審計總結審計思路sql注入靶場分析

将反序列化的結果通過token值傳遞給php頁面。發現一直報錯。然後修改源代碼檢視user的值發現隻有一個i。

PHP代碼審計總結審計思路sql注入靶場分析

最後發現壓縮的時候将token值壓縮成了一個數組,是以我們構造數組token,也就是建立一個數組,值為ichunqiu就行。

PHP代碼審計總結審計思路sql注入靶場分析

然後以為成功的時候結果啥都不列印,搞得有點抓耳撓腮了。

PHP代碼審計總結審計思路sql注入靶場分析

後面檢視源代碼發現就隻将flag值傳遞給了info。flag值都沒有定義。

PHP代碼審計總結審計思路sql注入靶場分析

去修改源代碼,将flag值添加上去。

PHP代碼審計總結審計思路sql注入靶場分析

後面就成功了,是以說這題不止要修改代碼,還要建構代碼。

MD5相似繞過

先上源代碼

<?php

$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md51 == $md52) {
    echo "nctf{*****************}";
} else {
    echo "false!!!";
}}
else{echo "please input a";}

?>
           

上面函數說明,先将字元串進行md5加密,然後讓傳遞一個參數,他不能等于這個字元串,但是要讓他們的md5值相同。

是以我們要進行繞過。

==

對比的時候會進行資料轉換,

0eXXXXXXXXXX

轉成

了,如果比較一個數字和字元串或者比較涉及到數字内容的字元串,則字元串會被轉換為數值并且比較按照數值來進行。

是以我們隻需要建構前面兩個md5值為0e開頭就行了。

var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
var_dump('0010e2' == '1e3');
var_dump('0x1234Ab' == '1193131');
var_dump('0xABCdef' == ' 0xABCdef');

md5('240610708'); // 0e462097431906509019562988736854 
md5('QNKCDZO'); // 0e830400451993494058024219903391 
           

結果

PHP代碼審計總結審計思路sql注入靶場分析

密碼MD5比較繞過

<?php

if($_GET[user] && $_GET[pass]) {
  //  mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
  // mysql_select_db(SAE_MYSQL_DB);
  $user = $_GET[user];
  $pass = md5($_GET[pass]);
  $query = @mysql_fetch_array(mysql_query("select pw from ctf where user=' $user '"));

  if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) {

    //strcasecmp:0 - 如果兩個字元串相等

      echo "<p>Logged in! Key: ntcf{**************} </p>";
  }
  else {
    echo("<p>Log in failure!</p>");
  }
}

?>
           

這一題又是資料庫,是以隻能看出大概。他判斷的是查詢的值必須有然後他與輸入的值轉換成為md5後結果相同,是以我們可以将查詢哪裡閉合掉,建構一個md5值。進行繞過

payload:?user=’ union select ‘e10adc3949ba59abbe56e057f20f883e’ – k&pass=123456

PHP代碼審計總結審計思路sql注入靶場分析

因為沒有連接配接資料庫是以他隻能報錯。

18 md5()函數===使用數組繞過

<?php
error_reporting(0);
$flag = 'flag{test}';
if (isset($_GET['username']) and isset($_GET['password'])) {
    if ($_GET['username'] == $_GET['password'])
        print 'Your password can not be your username.';
    else if (md5($_GET['username']) === md5($_GET['password']))
        die('Flag: '.$flag);
    else
        print 'Invalid password';
}
?>
           

這題有三個判斷。

if (isset($_GET['username']) and isset($_GET['password']))
if ($_GET['username'] == $_GET['password'])
else if (md5($_GET['username']) === md5($_GET['password']))
           

第一個判斷是username和password不為空

第二個判斷是username和password要繞過使他不相同

第三個判斷是他們的md5值相同。

因為md5函數的特性是如果傳遞的不是字元串則傳回NULL

是以我們可以建構數組,使他繞過。

payload:?username[]=1&password[]=2.

PHP代碼審計總結審計思路sql注入靶場分析

十六進制的比較

<?php

error_reporting(0);
function noother_says_correct($temp)
{
    $flag = 'flag{test}';
    $one = ord('1');  //ord — 傳回字元的 ASCII 碼值
    $nine = ord('9'); //ord — 傳回字元的 ASCII 碼值
    $number = '3735929054';
    // Check all the input characters!
    for ($i = 0; $i < strlen($number); $i++)
    { 
        // Disallow all the digits!
        $digit = ord($temp{$i});
        if ( ($digit >= $one) && ($digit <= $nine) )
        {
            // Aha, digit not allowed!
            return "flase";
        }
    }
    if($number == $temp)
        return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);

?>
           

下面代碼表示循環周遊temp的值,也就是輸入的值,将值轉化為ascii碼傳遞給digit。

for ($i = 0; $i < strlen($number); $i++)
    { 
        // Disallow all the digits!
        $digit = ord($temp{$i});
           

然後比較不能輸入1,-9的數字,是以我們要繞過這裡。

if ( ($digit >= $one) && ($digit <= $nine) )
        {
            // Aha, digit not allowed!
            return "flase";
        }
    }
           

但是下面有必須讓輸入的值與它定義的值相等。

if($number == $temp)
        return $flag;
}
           

是以我們可以利用十六進制數進行與十進制比較,因為同一個十六進制數與十進制數比較結果相等。

我們用函數将定義的number值用十六進制輸出出來。

PHP代碼審計總結審計思路sql注入靶場分析

然後将輸出的十六進制數傳遞給password。

payload:?password=0xdeadc0de

PHP代碼審計總結審計思路sql注入靶場分析

ereg_replace的\e參數漏洞

上源碼:

<?php $id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
    return preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
}
foreach ($_GET as $re => $str) {
    echo complex($re, $str) . "\n";
}
function getFlag() {
    @eval($_GET['cmd']);
};
           

這個案例實際上很簡單,就是 preg_replace 使用了

/e

模式,導緻可以代碼執行,而且該函數的第一個和第三個參數都是我們可以控制的。我們都知道, preg_replace 函數在比對到符号正則的字元串時,會将替換字元串(也就是上圖 preg_replace 函數的第二個參數)當做代碼來執行,然而這裡的第二個參數卻固定為 ‘strtolower("\1")’ 字元串,那這樣要如何執行代碼呢?

是以我們構造的payload為GET參數一個\S值為

${phpinfo()}

這個payload的意思就是執行phpinfo這個指令。\S就是所有空字元。

因為需要擷取flage值,我們需要傳遞利用getFlag函數裡面的指令執行漏洞進行注入。但是原函數又沒有執行這個函數,所有我們需要構造payload進行執行這個函數。

payload

?\S*=KaTeX parse error: Expected 'EOF', got '&' at position 12: {getFlag()}&̲cmd=system("ls …{getFlag()}&cmd=system(cat /flag");

危險函數示例

strcmp危險函數

還是分析源碼

<?php
$flag = "flag";
if (isset($_GET['a'])) {  
    if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 傳回 < 0; 如果 str1大于 str2傳回 > 0;如果兩者相等,傳回 0。 

    //比較兩個字元串(區分大小寫) 
        die('Flag: '.$flag);  
    else  
        print 'No';  
}

?>
           

這題沒啥分析的,看到strcmp函數就可以構造了。

還是先了解一下strcmp是個啥吧。

一般形式:strcmp(字元串1,字元串2)

說明:

當s1<s2時,傳回為負數 注意不是-1

當s1==s2時,傳回值= 0

當s1>s2時,傳回正數 注意不是1

即:兩個字元串自左向右逐個字元相比(按ASCII值大小相比較),直到出現不同的字元或遇’\0’為止。如:

“A”<“B” “a”>“A” “computer”>“compare”

特别注意:strcmp(const char *s1,const char * s2)這裡面隻能比較字元串,不能比較數字等其他形式的參數。

是以說隻要構造參數他不為字元串到時候就會報錯,他的傳回值就是0

是以我們可以建構a的參數為數組。

PHP代碼審計總結審計思路sql注入靶場分析

sha()危險函數

<?php

$flag = "flag";

if (isset($_GET['name']) and isset($_GET['password'])) 
{
    if ($_GET['name'] == $_GET['password'])
        echo '<p>Your password can not be your name!</p>';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
      die('Flag: '.$flag);
    else
        echo '<p>Invalid password.</p>';
}
else
    echo '<p>Login first!</p>';
?>
           

這一題也不用分析了,因為他第一個判斷需要name和password的值

是以第一個很好繞過。

第二個繞過直接用sha函數的漏洞,他不能對數組進行一系列操作,是以我們可以利用數組。

payload:?name[]=it&password[]=1

PHP代碼審計總結審計思路sql注入靶場分析

intval取整函數繞過

完整代碼

<?php

if($_GET[id]) {
   mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
  mysql_select_db(SAE_MYSQL_DB);
  $id = intval($_GET[id]);
  $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
  if ($_GET[id]==1024) {
      echo "<p>no! try again</p>";
  }
  else{
    echo($query[content]);
  }
}

?>
           

上面一堆代碼看不懂,然後看到下面判斷,和intval取整函數就知道怎麼繞過了。

起初還以為是sql注入繞過。後面發現可以直接使用intval這個取整危險函數繞過就行。

intval:這個函數時取整函數,遇上數字或正負符号才開始做轉換,再遇到非數字或字元串結束時(\0)結束轉換。

是以這裡,直接輸入一個小數,讓他繞過下面沒有取整的判斷。

PHP代碼審計總結審計思路sql注入靶場分析

上面的報錯隻是因為我沒有那個資料庫,然後連接配接失敗這些。整體來說還是正确的。

19 ereg()函數strpos() 函數用數組傳回NULL繞過

<?php  

$flag = "flag";  
   
if (isset ($_GET['password'])) {  
    if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)  
        echo 'You password must be alphanumeric';  
    else if (strpos ($_GET['password'], '--') !== FALSE)  
        die('Flag: ' . $flag);  
    else  
        echo 'Invalid password';  
}  
?>
           

這題之前做過類似的,可以使用%00截斷。

payload :?password=lj%00–

PHP代碼審計總結審計思路sql注入靶場分析

弱類型整數大小比較繞過(is_numeric)

<?php

error_reporting(0);
$flag = "flag{test}";

$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;    
if($temp>1336){
    echo $flag;
} 

?>
           

這個是危險函數的執行個體

is_numeric的特性:
當數字加字元串的時候會直接取數字,忽略掉字元。
           

是以我們隻需要構造大于1336的數加上一個字元串就OK了。

payload:?password=1337b

PHP代碼審計總結審計思路sql注入靶場分析

md5函數驗證繞過

<?php

error_reporting(0);
$flag = 'flag{test}';
$temp = $_GET['password'];
if(md5($temp)==0){
    echo $flag;
}

?>
           

這題直接利用md5函數的危險性,傳遞一個空數組就過去了。

PHP代碼審計總結審計思路sql注入靶場分析

頭部驗證繞過

session驗證繞過

<?php

$flag = "flag";

session_start(); 
if (isset ($_GET['password'])) {
    if ($_GET['password'] == $_SESSION['password'])
        die ('Flag: '.$flag);
    else
        print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>
           

這段代碼其實就一句關鍵代碼

PHP代碼審計總結審計思路sql注入靶場分析

隻需要繞過這個password就行,因為password在未登入的情況下他的session值為空,我們隻要上傳password的值為空就可以繞過。

payload:?password=

PHP代碼審計總結審計思路sql注入靶場分析

X_FORWARDED_FOR頭部繞過

還是先看源代碼吧

<?php
function GetIP(){
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
    $cip = $_SERVER["HTTP_CLIENT_IP"];
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
    $cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if(!empty($_SERVER["REMOTE_ADDR"]))
    $cip = $_SERVER["REMOTE_ADDR"];
else
    $cip = "0.0.0.0";
return $cip;
}

$GetIPs = GetIP();
if ($GetIPs=="1.1.1.1"){
echo "Great! Key is *********";
}
else{
echo "錯誤!你的IP不在通路清單之内!";
echo $GetIPs;
}
?>
           

這段代碼很簡單,就是要修改自己的ip,讓他繞過,是以我們可以一個個地試。

先嘗試用burp進行抓包。将抓到的包發送到repeater子產品。

PHP代碼審計總結審計思路sql注入靶場分析

然後利用裡面的判斷進行嘗試,嘗試修改自己的ip位址為1.1.1.1

ps:這裡有坑,這應該是橫線,而不是下劃線。

PHP代碼審計總結審計思路sql注入靶場分析
PHP代碼審計總結審計思路sql注入靶場分析

先來個總結吧,修改ip位址的頭部參數

Client-ip:	用戶端ip
x-forward-for:XFF頭	HTTP請求端真實的IP
remote-addr:
           

前兩個可以進行僞造,最後一個雖然可以僞造但是實作有點困難。

其他

php://input和substr繞過。

這題太難了。上源代碼。

<?php
if(!$_GET['id']) {
	header('Location:
  index.php?id=1');
	exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];

if(stripos($a,'.')){
	echo 'Hahahahahaha';
	return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) {
	echo "flag is me";
} else {
	echo $data;
	print "work harder!harder!harder!";
}
?>
``
先分析吧。首先我們要先繞過下面的判斷

```php
if(!$_GET['id']) {
	header('Location:
  index.php?id=1');
	exit();
}
           

這個判斷号繞過,在數字後面加一個字母

payload:?id=1a

最難的是下面的判斷。

if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) {
	echo "flag is me";
} 
           

這段賊難,還是先補充一下其他的知識吧。

eregi(pattern,string)	這個是正則比對的函數,第一個參數是正則比對的參數,第二個參數是在那裡面比對。
例如:eregi("111","1114")在1114裡面比對111

substr(str,str.list,length):截取字元串的函數,第一個參數是需要截取的字元串,第二個參數是從第幾個下标開始截,第三個參數是截幾個。
例如:substr("flag is me",0,4):從第一個開始截取4個字元。就是flag

           

函數明白了在去嘗試吧。試了很多次,就不依依截下來了,就發成功答案吧。

payload:?id=0a&a=1.txt&b=%0011111

PHP代碼審計總結審計思路sql注入靶場分析

那三個判斷成功之後結果他還是判斷失敗,在來找一下錯誤發現data裡面的内容是空的。這下又沒有辦法了。又繼續嘗試。

下面又得補充知識了

php://input 是個可以通路請求的原始資料的隻讀流。 
POST 請求的情況下,最好使用 php://input 來代替 $HTTP_RAW_POST_DATA,因為它不依賴于特定的 php.ini 指令。 
而且,這樣的情況下 $HTTP_RAW_POST_DATA 預設沒有填充, 比激活 always_populate_raw_post_data 潛在需要更少的記憶體。 
enctype="multipart/form-data" 的時候 php://input 是無效的。
           

是以我們将a的參數修改,利用bp進行抓包,将上傳方法改為post,添加要上傳的參數。

payload:?id=0a&a=php://input&b=%0011111

PHP代碼審計總結審計思路sql注入靶場分析
PHP代碼審計總結審計思路sql注入靶場分析