sqlmap 中的装饰器

sqlmap 中的装饰器

我们可以在 sqlmap\lib\core\decorators.py 中看到有定义三个装饰器:

装饰器-cachedmethod

cachedmethod 装饰器是 sqlmap 手动实现的 函数缓存器 , 目的是缓存高频调用函数的结果,用户优化执行速度。

在 sqlmap 中,sqlmap 自己实现了函数缓存器,保存的 key 值是对传入函数参数合并并进行 hash 作为 key 值,代码如下:

def cachedmethod(f):
    # 一种固定大小的类似dict的容器,一旦超过大小限制,就会逐出最近使用最少的(lru)项 。
    _cache[f] = LRUDict(capacity=MAX_CACHE_ITEMS)

    @functools.wraps(f)
    def _f(*args, **kwargs):
    # 手动实现了函数缓存
        try:
            # 将参数进行hash保存
            key = int(hashlib.md5("|".join(str(_) for _ in (f, args, kwargs)).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff
        except ValueError:  # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value)
            result = f(*args, **kwargs)
        else:
            try:
                with _cache_lock:
                    # 尝试直接从缓存字典中取出该函数 key值为对应hash值的 已缓存返回值
                    result = _cache[f][key]
            except KeyError:
                # 如果缓存中没有,则调用该函数计算,并尝试保存返回值到LRUDict中
                result = f(*args, **kwargs)
                with _cache_lock:
                    _cache[f][key] = result
        return result
    return _f

sqlmap 使用 cachedmethod 装饰器装饰的都是位于
sqlmap/lib/core/common.py 中,有 extractRegexResult (返回正则匹配对应字符串中的内容),getPublicTypeMembers (返回指定类型中的成员),aliasToDbmsEnum (根据名称返回 DBMS 数据库名称)等函数。主要是一些调用频繁,并且传入参数固定并且返回值也会固定的函数。

装饰器-stackedmethod

首先看被装饰的函数, 位于 sqlmap 的 sqlmap/lib/ 目录下的各个 py 文件中。然后看注释描述说是进行栈平衡,并且装饰的是那些使用了 pushValuepopValue 的函数(有点不知道所以然)。接下来看具体的代码内容,如下:

def stackedmethod(f):
    """
    Method using pushValue/popValue functions (fallback function for stack realignment)

    >>> threadData = getCurrentThreadData()
    >>> original = len(threadData.valueStack)
    >>> __ = stackedmethod(lambda _: threadData.valueStack.append(_))
    >>> __(1)
    >>> len(threadData.valueStack) == original
    True
    """

    @functools.wraps(f)
    def _(*args, **kwargs):
        threadData = getCurrentThreadData()
        originalLevel = len(threadData.valueStack)

        try:
            result = f(*args, **kwargs)
        finally:
            if len(threadData.valueStack) > originalLevel:
                threadData.valueStack = threadData.valueStack[:originalLevel]

        return result

    return _

可以看到 stackmethod 该装饰器作用就是维持 threadData.valueStack 在函数调用前和函数调用后的长度保持不变,也即栈平衡。那么为什么要保持函数调用前后的栈平衡呢。

我们再看被装饰的函数,可以看到被装饰的函数中都调用了 pushValuepopValue 的函数,主要是用来保存变量值,而 threadData.valueStack 是 sqlmap 维护的线程中的栈,用来保存临时变量。正常情况下,push 和 pop 是保持平衡的如下,而加入某个函数内部出现栈不平衡的情况,pushValue 没有进行 POP,则会影响后续的函数 push 和 pop,因此通过装饰器直接在函数调用结束后进行栈回收能有效防止该情况。(非常类型汇编代码中函数调用前后的堆栈回收)。

@stackedmethod
def checkSuhosinPatch(injection):
    """
    Checks for existence of Suhosin-patch (and alike) protection mechanism(s)
    """

    if injection.place in (PLACE.GET, PLACE.URI):
        debugMsg = "checking for parameter length "
        debugMsg += "constraining mechanisms"
        logger.debug(debugMsg)
		# 保存注入点
        pushValue(kb.injection)

        kb.injection = injection
        randInt = randomInt()

        if not checkBooleanExpression("%d=%s%d" % (randInt, ' ' * SUHOSIN_MAX_VALUE_LENGTH, randInt)):
            warnMsg = "parameter length constraining "
            warnMsg += "mechanism detected (e.g. Suhosin patch). "
            warnMsg += "Potential problems in enumeration phase can be expected"
            logger.warning(warnMsg)
		# 重新取出注入点,存入到KB中
        kb.injection = popValue()

装饰器-lockedmethod

被 lockedmethod 装饰的函数比较少,仅有 sqlmap/lib/request/basic.py::forgeHeaderssqlmap/lib/request/inject.py::getValue 两个函数,代码也比较明确,在调用对应函数进行加锁,保证当前被装饰的方法只能在某个时刻由一个线程执行。

def lockedmethod(f):
    @functools.wraps(f)
    def _(*args, **kwargs):
        if f not in _method_locks:
            _method_locks[f] = threading.RLock()
        with _method_locks[f]:
            result = f(*args, **kwargs)
        return result
    return _