2

这是一个非常简单的系统,基于发送似乎存在安全漏洞的 JSON 消息。有一个 Python 服务器(使用标准库中包含的 JSON 模块)接收 JSON 对象并对其进行操作。如果它得到{"req": "ping"},它只是返回{"resp": "pong"}。它还有一个用于设置音量的命令,以及一个用于更改管理员密码的命令。管理员可以将任何 JSON 发送到此服务器。这是(server.py):

import json
import sys

def change_admin_password(p): pass # empty for test
def set_volume(v): pass # empty for test

def handle(js):
    if (js["req"] == "ping"):
        return {"resp": "pong"}
    if (js["req"] == "change admin password"):
        change_admin_password(js["args"]["password"])
        return {"resp": "ok"}
    if (js["req"] == "set volume"):
        set_volume(int(js["args"]["volume"]))
        return {"resp": "ok"}

print handle(json.load(sys.stdin))

它从标准输入读取命令并处理它。另一个脚本将从网络套接字读取 JSON 对象并将它们传递给该脚本。

其他用户必须通过使用libjson用 C++ 编写的代理。它只是阻止需要管理员权限的请求。例如,如果用户尝试更改管理员密码,代理将拒绝该命令:

$ echo '{"req": "change admin password", "args": {"password":"new"}}' | ./proxy 
echo $?
1

这是代码(proxy.cpp):

#include "libjson.h"
using namespace std;
int main() {
    string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
    JSONNode n = libjson::parse(json);
    string req = n.at("req").as_string();
    if (req == "change admin password") {
        return 1;
    }
    cout << n.write();
}

要使用代理,管理套接字的主脚本将通过代理传输数据并将其输出到 Python 服务器:

$ echo '{"req": "ping"}' | ./proxy | python server.py
{'resp': 'pong'}
$ echo '{"req": "set volume", "args": {"volume": 50}}' | ./proxy | python server.py 
{'resp': 'ok'}

如果用户尝试使用受限命令,它将按预期失败:

$ echo '{"req": "change admin password", "args": {"password": "new"}}' | ./proxy | python server.py 
Traceback (most recent call last):
  File "server.py", line 17, in <module>
[...]

但是由于某种原因,如果“req”键在 JSON 中出现两次(不应该是非法的吗?),非管理员用户可以更改管理员密码:

$ echo '{"req": "nothing", "req": "change admin password", "args": {"password": "new"}}' | ./proxy | python server.py 
{'resp': 'ok'}

为什么?


尝试 Mike McMahon 的解决方法:

我尝试使用JSONNode.find它作为解决方法,但它似乎不起作用。

我试着迭代所有元素:

#include "libjson.h"
using namespace std;
int main() {
    string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
    JSONNode n = libjson::parse(json);
    for (JSONNode::json_iterator it = n.begin(); it != n.end(); it++) {
        cout << "found one: " << it->at("x").as_string() << endl;
    }
}

这有效:

$ g++ proxy.cpp libjson.a  -o proxy && ./proxy
In file included from libjson.h:4:0,
                 from proxy.cpp:1:
_internal/Source/JSONDefs.h:157:6: warning: #warning , Release build of libjson, but NDEBUG is not on [-Wcpp]
{"a": {"x": 1}, "b": {"x": 2}}
found one: 1
found one: 2

除了 JSON 无效时会出现段错误吗?我使用迭代器错了吗?

$ echo '{"a": {"x": 1}, {"b": {"x": 2}}' | ./proxy 
found one: 1
Segmentation fault

我将其替换n.begin()n.find("y")

#include "libjson.h"
using namespace std;
int main() {
    string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
    JSONNode n = libjson::parse(json);
    for (JSONNode::json_iterator it = n.find("y"); it != n.end(); it++) {
        cout << "found one: " << it->at("x").as_string() << endl;
    }
}

它根本不起作用。我使用迭代器错了吗?

g++ proxy.cpp libjson.a  -o proxy && ./proxy
In file included from libjson.h:4:0,
                 from proxy.cpp:1:
_internal/Source/JSONDefs.h:157:6: warning: #warning , Release build of libjson, but NDEBUG is not on [-Wcpp]
{"y": {"x": 1}}
Segmentation fault

解决方法的另一种尝试:

#include "libjson.h"
using namespace std;
int main() {
    string json((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());
    JSONNode n = libjson::parse(json);
    for (JSONNode::json_iterator it = n.begin(); it != n.end(); it++) {
        if (it->name() == "req" && it->as_string() == "change admin password")
        {
            cout << "found one " << endl;
        }
    }
}

有用!

$ echo '{"req": "change admin password"}' | ./proxy
found one 
$ echo '{"req": "x", "req": "change admin password"}' | ./proxy
found one 
$ echo '{"req": "change admin password", "req": "x"}' | ./proxy
found one 

但是仍然存在无效 JSON 输入的段错误?

$ echo '{"req": "x", {"req": "x"}' | ./proxy
Segmentation fault
4

1 回答 1

2

libjson http://sourceforge.net/p/libjson/bugs/47/中的已知错误


考虑 JSON:

{ "same" : 4, "same": 5}  

如果你打入 JSON,你会看到一个错误(重复的键名

如果您输入 JSON,您将不会看到错误,但 JSON 将被重新格式化并删除第一个“相同”键。

一个简短的环顾表明标准说你“不应该”有重复的键名,而不是“不能”,所以从技术上讲 libjson 是可以的。

您的 API(即 at(string)、operator)与这两个网站(上图)中的任何一个都不一致。

  • jslint 显示错误
  • jsonlint 杀死第一个节点,使节点的值为 5,我认为这与 javascript 的覆盖行为一致。

libjson.at(string) 函数将返回第一个条目(而不是最后一个)。

JSONNode.find("req") 的解决方法

看find方法

JSONNode.find("req"); 

返回指向节点数组的指针,您可以使用它来确定它是否已被多次指定。虽然库应该覆盖后续条目之上的每个条目(并且根据有效的 JSON,不这样做是一个错误) - 您可以使用 find 方法来定位并获取迭代器以遍历匹配的节点。

JSONNode n = libjson::parse(json)
JSONNode::json_iterator it = n.find("req");
// iterate over the array
if (it[i].at("req").as_string() == "change admin password") {
  return 1;
}

请注意,这不是世界上最好的解决方法,因为我想这会返回与参数匹配的所有节点,并且在复杂的结构中,可能会有多个同名节点嵌套在其他地方,对于您的需要,这应该就足够了。

但是,如果这很关键,或者可能支持管理员区分命令,例如areq管理请求,您应该查看另一个库来验证您的 JSON 调用。这将允许您清除代理上包含 areq 命令的任何请求,因为只有管理员才能生成此类请求(并且显然不能通过代理发送)。

任何包含管理命令的标准请求都会失败。

更新

尝试使用迭代器,诚然 C++ 不是我的主要语言,而且我并不精通 STL 迭代器。

for (JSONNode::json_iterator jsonIter = n.begin(); jsonIter != n.end(); jsonIter++) {
    if (jsonIter->name() == "req" &&
        jsonIter->as_string() == "change admin password") 
    {
        // found something do magic here
    }
}

上述代码在我的测试中对我有用。


非法与否,您需要在您的代理和服务器中清除此内容。它在大多数将键连接起来的库中滥用了一个可能的漏洞(因为您不能有重复的键)。寻找它并在它不被视为 JSON 对象的任何地方擦掉它。

或者在您的代理之前放置另一层,该层采用 JSON 并通过解析器运行它 - 这将删除任何重复的键。

编辑

python 中的 JSON lib 获取第一个定义req并应用它的内容,然后在它的第二个定义上req覆盖第一个定义并设置利用者命令。

由于您的 python 应用程序盲目地假设任何传入的请求都已被预先清理(坏主意),因此您需要在代理层对此进行清理,并可能考虑使用 API 密钥来处理管理员级别的请求,以便获得通过或绕过您的代理仍然需要知道某种 KEY。

于 2013-10-21T04:30:46.110 回答