json变量的声明在哪里,传递过程?
前言
漏洞公布已经有几天时间了,凑个周末也看了看,ispirit/im/upload.php
已经有很多前辈分析过了,这里就不在赘述,在分析复现过程中发现了一些问题记录一下,分析的版本主要是2015和v11,在源码解密中,测试了v11和2015,2015使用的是zend5.3,v11使用的是zend5.4。
文件上传1
另一处未授权文件上传:
general/file_folder/swfupload.php
引入的文件中没有对权限的校验,首先获取了$_POST["PHPSESSID"]
,设置会话id,此处没有什么限制继续向下看
Line:23-29 典型的变量覆盖
这里判断了是否是post上传文件以及文件上传中是否产生错误,只要post构造表单传入一个正常大小的文件即可满足。
Line 35~61 需要传入参数FILE_SORT
,满足条件则会执行数据库查询当前用户的文件容量,这里看到了SQL语句中拼接了$_SESSION["LOGIN_UID"]
,再结合上边的变量覆盖是不是可以导致注入呢?答案是不行的,因为在上文引用文件中引入了inc/common.inc.php
function CheckRequest(&$val)
{
if (is_array($val)) {
foreach ($val as $_k => $_v ) {
checkrequest($_k);
checkrequest($val[$_k]);
}
}
else {
if ((0 < strlen($val)) && preg_match("#^(MYOA_|GLOBALS|_GET|_POST|_COOKIE|_ENV|_SERVER|_FILES|_SESSION)#", $val)) {
exit("Invalid Parameters!");
}
}
}
....
checkrequest($_REQUEST);
if (0 < count($_COOKIE)) {
foreach ($_COOKIE as $s_key => $s_value ) {
$_COOKIE[$s_key] = strip_tags(securerequest($s_value));
$$s_key = $_COOKIE[$s_key];
}
reset($_COOKIE);
}
if (0 < count($_POST)) {
$arr_html_fields = array();
foreach ($_POST as $s_key => $s_value ) {
if (substr($s_key, 0, 15) != "TD_HTML_EDITOR_") {
if (is_array($s_value)) {
$_POST[$s_key] = securerequest($s_value);
}
else {
$_POST[$s_key] = strip_tags(securerequest($s_value));
}
$$s_key = $_POST[$s_key];
}
else {
unset($_POST[$s_key]);
$s_key = substr($s_key, 15);
$$s_key = securerequest($s_value);
$arr_html_fields[$s_key] = $$s_key;
}
}
reset($_POST);
$_POST = array_merge($_POST, $arr_html_fields);
}
if (0 < count($_GET)) {
foreach ($_GET as $s_key => $s_value ) {
$_GET[$s_key] = strip_tags(securerequest($s_value));
$$s_key = $_GET[$s_key];
}
reset($_GET);
}
作用就是对传入进来的内容进行正则判断,如果存在_COOKIE、_SESSION
等字符串则进行拦截,所以此处无法利用,继续向下看。
重点在于Line:91-93和97-98
这里将插入数据库的内容写入到了当前目录下的aa.txt
或者bb.txt
。
在流程的最后返回了一串md5加密的字符,那么上传文件的地址就需要从上文中的aa.txt
和bb.txt
来获得。
构造上传数据包:
可以看到文件名是随机字符+.+原文件名
因为在上传表单中传入了SORT_ID=1
所以sql语句会保存在bb.txt
拼接起来就是我们上传后的文件名了/general/../../attach/file_folder/2003/xxx.xxxx.xxx
在v11版本中此文件进行了修改删除了保存到文件的代码。
if ($SORT_ID == "0") {
$query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','" . $_SESSION["LOGIN_USER_ID"] . "','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";
exequery(TD::conn(), $query);
}
else {
$query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";
exequery(TD::conn(), $query);
$CONTENT_ID = mysql_insert_id();
add_log(16, _("新建文件,名称:") . $SUBJECT, $_SESSION["LOGIN_USER_ID"]);
但是在insert sql语句中可以看到拼接了$SORT_ID
,在exequery
函数中最后sql语句的执行会进行检查是否有敏感函数,有的话就会打印出错误的语句,相应文件在inc/conn.php
因为在执行sql语句之前文件已经上传成功,所以语句的错误并不妨碍文件上传。
$query = "insert into FILE_CONTENT(SORT_ID,SUBJECT,CONTENT,SEND_TIME,ATTACHMENT_ID,ATTACHMENT_NAME,ATTACHMENT_DESC,USER_ID,CONTENT_NO,CREATER) values ($SORT_ID,'$SUBJECT','','$SEND_TIME','$ATTACHMENT_ID','$ATTACHMENT_NAME','$ATTACHMENT_DESC','','$CONTENT_NO','" . $_SESSION["LOGIN_USER_ID"] . "')";
sort_id可控所以这里也是一个insert注入
在file_content
表中sort_id
的是int(11)
,所以要使用字符截断控制长度,但是由于没有回显和过滤了一些函数,需要找到二次注入点或者找到一个可以显示sort_id
的地方,由于还要写论文(毕业重要),时间问题就没有继续寻找。
失败的文件写入
搜索file_put_contents
,文件general/workflow/document_list/input_form/form6.php
没有校验权限,并将$MAINDOC_ID
写入到29.txt
而变量$MAINDOC_ID
可以结合变量覆盖漏洞来传入,因为在gateway.php
中的引入过程中,引入了inc/common.inc.php
结果是因为strip_tags
的处理,无法输入php标签,此处利用失败
文件包含
<?php
ob_start();
include_once "inc/session.php";
include_once "inc/conn.php";
include_once "inc/utility_org.php";
if ($P != "") {
if (preg_match("/[^a-z0-9;]+/i", $P)) {
echo _("非法参数");
exit();
}
session_id($P);
session_start();
session_write_close();
if (($_SESSION["LOGIN_USER_ID"] == "") || ($_SESSION["LOGIN_UID"] == "")) {
echo _("RELOGIN");
exit();
}
}
if ($json) {
$json = stripcslashes($json);
$json = (array) json_decode($json);
foreach ($json as $key => $val ) {
if ($key == "data") {
$val = (array) $val;
foreach ($val as $keys => $value ) {
$keys = $value;
}
}
if ($key == "url") {
$url = $val;
}
}
if ($url != "") {
if (substr($url, 0, 1) == "/") {
$url = substr($url, 1);
}
if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
include_once $url;
}
}
exit();
}
?>
?>
对$P
进行了是否为空、正则校验以及当前用户是否登录,只需要使$P
为空即可,在下面的if $json
分支中使用了include_once $url
,所以只要在传入的json数据中使URl参数中包含ispirit/
、general/
、module/
再跳转目录到包含的文件即可进行任意文件包含。
payload:json={"url":"xxx"}
v11测试:
参考
文笔不好,内容某个方面或许偏颇,不足之处欢迎师傅前辈们指点和纠正,感激不尽。