#!/usr/bin/env python

"""
vulnserver.py - Trivial SQLi vulnerable HTTP server (Note: for testing purposes)

Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 该脚本实现了一个简单的 HTTP 服务器，故意留下 SQL 注入漏洞，供测试和学习之用。
# 它展示了基本的 Web 服务器如何处理请求、解析参数以及如何与 SQLite 数据库交互。
# 此类工具应仅在安全的实验环境中使用，以避免滥用和安全风险。在实际应用中，开发者应注意保护数据库查询以防止 SQL 注入攻击。

from __future__ import print_function

import base64
import json
import re
import sqlite3
import sys
import threading
import traceback

# 检查 Python 版本
PY3 = sys.version_info >= (3, 0)
UNICODE_ENCODING = "utf-8"  # 定义 Unicode 编码
DEBUG = False  # 调试模式标志

if PY3:
    # 导入 Python 3 中的 HTTP 相关模块
    from http.client import INTERNAL_SERVER_ERROR, NOT_FOUND, OK
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from socketserver import ThreadingMixIn
    from urllib.parse import parse_qs, unquote_plus
else:
    # 对于 Python 2，导入相应的 HTTP 相关模块
    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
    from httplib import INTERNAL_SERVER_ERROR, NOT_FOUND, OK
    from SocketServer import ThreadingMixIn
    from urlparse import parse_qs
    from urllib import unquote_plus

# SQLite 模式定义，创建用户表并插入示例数据
SCHEMA = """
    CREATE TABLE users (
        id INTEGER,
        name TEXT,
        surname TEXT,
        PRIMARY KEY (id)
    );
    INSERT INTO users (id, name, surname) VALUES (1, 'luther', 'blisset');
    INSERT INTO users (id, name, surname) VALUES (2, 'fluffy', 'bunny');
    INSERT INTO users (id, name, surname) VALUES (3, 'wu', '179ad45c6ce2cb97cf1029e212046e81');
    INSERT INTO users (id, name, surname) VALUES (4, 'sqlmap/1.0-dev (https://sqlmap.org)', 'user agent header');
    INSERT INTO users (id, name, surname) VALUES (5, NULL, 'nameisnull');
"""

# 定义监听地址和端口
LISTEN_ADDRESS = "localhost"
LISTEN_PORT = 8440

# 全局变量
_conn = None
_cursor = None
_lock = None
_server = None
_alive = False

def init(quiet=False):
    global _conn
    global _cursor
    global _lock

    # 在内存中创建 SQLite 数据库连接
    _conn = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False)
    _cursor = _conn.cursor()  # 创建游标，用于执行 SQL 命令
    _lock = threading.Lock()  # 创建线程锁，以处理多线程环境下的数据库访问

    _cursor.executescript(SCHEMA)  # 执行脚本以创建表并插入数据

    if quiet:
        # 如果 quiet 为 True，则禁止输出
        global print

        def _(*args, **kwargs):
            pass

        print = _

class ThreadingServer(ThreadingMixIn, HTTPServer):
    # 允许多线程处理 HTTP 请求
    def finish_request(self, *args, **kwargs):
        try:
            HTTPServer.finish_request(self, *args, **kwargs)
        except Exception:
            if DEBUG:
                traceback.print_exc()

class ReqHandler(BaseHTTPRequestHandler):
    # 请求处理类
    def do_REQUEST(self):
        # 处理请求，分割路径和查询字符串
        path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "")
        params = {}

        if query:
            params.update(parse_qs(query))  # 解析查询字符串为字典

            # 检查是否出现恶意脚本
            if "<script>" in unquote_plus(query):
                self.send_response(INTERNAL_SERVER_ERROR)
                self.send_header("X-Powered-By", "Express")
                self.send_header("Connection", "close")
                self.end_headers()
                self.wfile.write("CLOUDFLARE_ERROR_500S_BOX".encode(UNICODE_ENCODING))
                return

        # 处理请求数据（如果有）
        if hasattr(self, "data"):
            if self.data.startswith('{') and self.data.endswith('}'):
                params.update(json.loads(self.data))  # JSON 数据
            elif self.data.startswith('<') and self.data.endswith('>'):
                # 解析 HTML 表单数据
                params.update(dict((_[0], _[1].replace("&apos;", "'").replace("&quot;", '"')
                                    .replace("&lt;", '<').replace("&gt;", '>').replace("&amp;", '&'))
                                   for _ in re.findall(r'name="([^"]+)" value="([^"]*)"', self.data)))
            else:
                # 处理 URL 编码数据
                self.data = self.data.replace(';', '&')  # 兼容性处理
                params.update(parse_qs(self.data))

        # 处理请求头参数
        for name in self.headers:
            params[name.lower()] = self.headers[name]

        # 处理 Cookie 参数
        if "cookie" in params:
            for part in params["cookie"].split(';'):
                part = part.strip()
                if '=' in part:
                    name, value = part.split('=', 1)
                    params[name.strip()] = unquote_plus(value.strip())

        # 将多值参数转换为单值
        for key in params:
            if params[key] and isinstance(params[key], (tuple, list)):
                params[key] = params[key][-1]

        self.url, self.params = path, params  # 存储 URL 和参数

        if self.url == '/':
            # 主页处理
            if not any(_ in self.params for _ in ("id", "query")):
                # 如果没有 ID 或查询参数，则显示主页面
                self.send_response(OK)
                self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
                self.send_header("Connection", "close")
                self.end_headers()
                self.wfile.write(b"<!DOCTYPE html><html><head><title>vulnserver</title></head><body><h3>GET:</h3><a href='/?id=1'>link</a><hr><h3>POST:</h3><form method='post'>ID: <input type='text' name='id'><input type='submit' value='Submit'></form></body></html>")
            else:
                code, output = OK, ""

                try:
                    # 处理回显参数
                    if self.params.get("echo", ""):
                        output += "%s<br>" % self.params["echo"]

                    if self.params.get("reflect", ""):
                        output += "%s<br>" % self.params.get("id")

                    with _lock:  # 使用锁来确保线程安全
                        if "query" in self.params:
                            # 执行任意 SQL 查询
                            _cursor.execute(self.params["query"])
                        elif "id" in self.params:
                            # 通过 ID 查询用户
                            if "base64" in self.params:
                                _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" %
                                                base64.b64decode("%s===" % self.params["id"],
                                                altchars=self.params.get("altchars")).decode())
                            else:
                                _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"])
                        results = _cursor.fetchall()  # 获取查询结果

                    output += "<b>SQL results:</b><br>\n"

                    # 根据查询结果决定响应的状态码和内容
                    if self.params.get("code", ""):
                        if not results:
                            code = INTERNAL_SERVER_ERROR  # 出错
                    else:
                        if results:
                            output += "<table border=\"1\">\n"

                            for row in results:
                                output += "<tr>"
                                for value in row:
                                    output += "<td>%s</td>" % value
                                output += "</tr>\n"

                            output += "</table>\n"
                        else:
                            output += "no results found"  # 查询无结果

                    output += "</body></html>"
                except Exception as ex:
                    code = INTERNAL_SERVER_ERROR
                    # 捕获异常并返回错误信息
                    output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex)

                # 发送响应
                self.send_response(code)
                self.send_header("Content-type", "text/html")
                self.send_header("Connection", "close")

                if self.raw_requestline.startswith(b"HEAD"):
                    self.send_header("Content-Length", str(len(output)))
                    self.end_headers()
                else:
                    self.end_headers()
                    self.wfile.write(output if isinstance(output, bytes) else output.encode(UNICODE_ENCODING))
        else:
            # 对于未定义的路径返回 404
            self.send_response(NOT_FOUND)
            self.send_header("Connection", "close")
            self.end_headers()

    def do_GET(self):
        self.do_REQUEST()  # 处理 GET 请求

    def do_PUT(self):
        self.do_POST()  # 处理 PUT 请求

    def do_HEAD(self):
        self.do_REQUEST()  # 处理 HEAD 请求

    def do_POST(self):
        # 处理 POST 请求
        length = int(self.headers.get("Content-length", 0))
        if length:
            # 读取请求体数据
            data = self.rfile.read(length)
            data = unquote_plus(data.decode(UNICODE_ENCODING, "ignore"))
            self.data = data
        elif self.headers.get("Transfer-encoding") == "chunked":
            # 处理 chunked 请求
            data, line = b"", b""
            count = 0

            while True:
                line += self.rfile.read(1)
                if line.endswith(b'\n'):
                    if count % 2 == 1:
                        current = line.rstrip(b"\r\n")
                        if not current:
                            break
                        else:
                            data += current

                    count += 1
                    line = b""

            self.data = data.decode(UNICODE_ENCODING, "ignore")

        self.do_REQUEST()  # 处理 POST 逻辑

    def log_message(self, format, *args):
        return  # 不记录日志

def run(address=LISTEN_ADDRESS, port=LISTEN_PORT):
    global _alive
    global _server
    try:
        _alive = True
        _server = ThreadingServer((address, port), ReqHandler)
        print("[i] running HTTP server at 'http://%s:%d'" % (address, port))
        _server.serve_forever()  # 开始监听并处理请求
    except KeyboardInterrupt:
        _server.socket.close()  # 关闭服务器
        raise
    finally:
        _alive = False

if __name__ == "__main__":
    try:
        init()  # 初始化数据库和服务器
        run(sys.argv[1] if len(sys.argv) > 1 else LISTEN_ADDRESS,
            int(sys.argv[2] if len(sys.argv) > 2 else LISTEN_PORT))
    except KeyboardInterrupt:
        print("\r[x] Ctrl-C received")  # 捕获 Ctrl-C 终止信号