1

挑战

我需要一个密码管理工具,它将被其他进程(各种脚本:python、php、perl 等)调用,它能够识别和验证调用者脚本以执行访问控制:要么返回密码返回或退出 -1

目前的实施

在研究了各种框架后,我决定使用能够处理 Keepass V1.X 后端数据库文件并构建我自己的访问控制覆盖python的 's (因为以后可以对其进行自定义并集成到我们的 LDAP 以进行用户/组访问) keepassdb. 访问控制是通过重载notes每个条目的字段以包含允许访问密码的 SHA-256 哈希列表来完成的。(请注意,这也验证了脚本没有被任何人更改)

密码管理器使用参数调用,-p该参数是被调用者脚本/应用程序的 PID,并将执行以下步骤:

  1. 从它自己的PID开始递归地“向上”查找并寻找父母。1在我们到达父 0的进程之前,必须找到调用者 PID。init这样我们就可以确定我们知道谁调用了这个密码管理器实例。
  2. 获取该(父)进程的完整命令行并分析它以查找脚本语言,包括 python、perl、php、bash、bat、groovy 等(shlex用于此)
  3. 找出脚本的绝对路径并计算其 SHA
  4. 将此与数据库值进行比较并查看它是否存在,如果存在,则允许脚本具有以标准格式在标准输出中返回的密码。如果不是,则以 -1 退出。

问题

上面的实现非常适合合法的脚本,但很容易混淆。让caller.py成为允许访问特定条目的脚本e。运行它的命令行看起来像python /path/to/caller.py arg1 arg2. 解析命令行的代码是:

cmd = walk_ppids(pid)
lg.debug(cmd)
if cmd is False:
    lg.error("PID %s is not my parent process or not a process at all!" % pid)
    sys.exit(-1)

cmd_parts = shlex.split(cmd)
running_script = ""
for p in cmd_parts:
    if re.search("\.(php|py|pl|sh|groovy|bat)$", p, re.I):
        running_script = p
        break

if not running_script:
    lg.error("Cannot identify this script's name/path!")
    sys.exit(-1)

running_script = os.path.abspath(running_script)
lg.debug("Found "+running_script)

phash = hash_file(open(running_script, 'rb'), hashlib.sha256())

使用以下方式获取父进程的命令行:

os.popen("ps -p %s -o args=" % ppid).read().strip()

现在,混淆上述函数的最简单方法是创建一个不带.sh扩展名的 shell 脚本,该扩展名将caller.py. sh 不使用它的参数,而是调用密码管理器查询条目e。命令行看起来像这样fake_sh ./caller.py,因此上面的代码返回 pass... 这是错误的做法。

问题

有人会认为这是很久以前解决的一个常见问题,没有程序员硬编码传递到脚本/应用程序中,但我做了几天的研究,我似乎无法找到任何以类似方式工作的东西。我知道这个问题比较开放,所以我会接受以下问题的答案:

  • 我在重新发明轮子吗?是否有可以做类似事情的框架/软件?
  • 这是依赖PID的正确方法吗?还有其他方法吗?
  • 实施方面,发布的代码是否可以改进为更健壮且不易混淆?(shlex分析部分)
4

1 回答 1

0

改进:使规则更加严格

第一步是确认正确的扩展在正确的解释器上运行,这意味着caller.py不能在/bin/bash.

类似的漏洞可以用 python 来利用,例如 command python -W ./caller.py ./myUberHack.py。寻找解释器的第一个参数的命令行分析器.py会认为它caller.py正在运行......但事实并非如此。

为所有解释器构建所有调用规则太耗时了,所以我对假设进行了硬编码。这些存储在 a 中tuple,每一行是:

(file extension, positional argument, interpreter first letters)
exts = (
    (".py", 1, "python"), 
    (".php", 2, "php"),
    (".pl", 1, "perl"),
    (".sh", 1, "/bin/bash"), # Assumption, we accept only bash 
    (".groovy", 1, "groovy"),
    (".rb", 1, "ruby"),
)
"""Matching extensions to positional arguments and interpreters"""

现在的验证码是:

for i in exts:
    # Check the specified cmdline position and extension
    if cmd_parts[i[1]].strip().endswith(i[0]):
        lg.debug("Checking "+cmd_parts[i[1]])
        running_script = cmd_parts[i[1]]

        # Make sure that the interpretter matches the extension
        if running_script.endswith(i[0]) and not cmd_parts[0].startswith(i[2]):
            lg.error("Wrong interpretter... go away...")
            sys.exit(-1)

        break

暂时想不出更好的了...

于 2015-12-20T13:43:24.140 回答