# 跟踪信息流

> 原文： [https://www.fuzzingbook.org/html/InformationFlow.html](https://www.fuzzingbook.org/html/InformationFlow.html)

我们已经探索了如何产生更好的输入，这些输入可以深入到所讨论的程序中。 这样做时，我们依靠程序崩溃来告诉我们我们已成功找到程序中的问题。 但是，这很简单。 如果程序的行为完全不正确，但不会导致崩溃怎么办？ 能做得更好吗？

在本章中，我们深入探讨如何在Python中跟踪信息流，以及如何使用这些流来确定程序是否表现出预期的行为。

**前提条件**

*   您应该已经阅读了关于的[一章。](Coverage.html)
*   您应该已经阅读了[关于概率模糊](ProbabilisticGrammarFuzzer.html)的章节。

我们首先建立基础架构，以便可以使用先前定义的功能。

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.InformationFlow](InformationFlow.html) import <identifier>

```

然后利用以下功能。

本章为Python *字符串*提供了两个包装器，使它们可以跟踪各种属性。 这些信息包括有关输入的安全性的信息以及有关输入字符串的原始索引的信息。

要跟踪有关安全属性的信息，请按以下方式使用`tstr`：

```py
>>> thello = tstr('hello', taint='LOW')

```

现在，来自`thello`的任何导致字符串片段的操作都将包含正确的异味。 例如：

```py
>>> thello[1:2].taint
'LOW'

```

要跟踪输入字符串中的原始索引，请按以下方式使用`ostr`：

```py
>>> ohw = ostr("hello\tworld", origin=100)

```

原始索引可以按以下方式恢复：

```py
>>> (ohw[0:4] +"-"+ ohw[6:]).origin
[100, 101, 102, 103, -1, 106, 107, 108, 109, 110]

```

## 脆弱的数据库

假设我们要在Python中实现*内存数据库*服务。 这是一个相当脆弱的尝试。 我们使用以下数据集。

```py
INVENTORY = """\
1997,van,Ford,E350
2000,car,Mercury,Cougar
1999,car,Chevy,Venture\
"""

```

```py
VEHICLES = INVENTORY.split('\n')

```

我们的数据库是一个Python类，它解析其参数并引发下面定义的`SQLException`。

```py
class SQLException(Exception):
    pass

```

该数据库只是一个仅通过SQL查询公开的Python `dict`。

```py
class DB:
    def __init__(self, db={}):
        self.db = dict(db)

```

### 代表表

该数据库包含表，这些表是通过方法调用`create_table()`创建的。 每个表数据结构都是一对值。 第一个是包含列名称和类型的元数据。 第二个值是表中的值列表。

```py
class DB(DB):
    def create_table(self, table, defs):
        self.db[table] = (defs, [])

```

可以使用`table()`方法调用使用名称来检索表。

```py
class DB(DB):
    def table(self, t_name):
        if t_name in self.db:
            return self.db[t_name]
        raise SQLException('Table (%s) was not found' % repr(t_name))

```

这是如何同时使用两者的示例。 我们用四列填充表`inventory`：`year`，`kind`，`company`和`model`。 最初，我们的表为空。

```py
def sample_db():
    db = DB()
    inventory_def = {'year': int, 'kind': str, 'company': str, 'model': str}
    db.create_table('inventory', inventory_def)
    return db

```

使用`table()`，我们可以检索表定义及其内容。

```py
db = sample_db()
db.table('inventory')

```

```py
({'year': int, 'kind': str, 'company': str, 'model': str}, [])

```

我们还定义了`column()`，用于从表声明中检索列定义。

```py
class DB(DB):
    def column(self, table_decl, c_name):
        if c_name in table_decl: 
            return table_decl[c_name]
        raise SQLException('Column (%s) was not found' % repr(c_name))

```

```py
db = sample_db()
decl, rows = db.table('inventory')
db.column(decl, 'year')

```

```py
int

```

### 执行SQL语句

`DB`的`sql()`方法执行SQL语句。 它检查其参数，并根据要执行的SQL语句的类型调度查询。

```py
class DB(DB):
    def do_select(self, query):
        assert False
    def do_update(self, query):
        assert False
    def do_insert(self, query):
        assert False
    def do_delete(self, query):
        assert False

    def sql(self, query):
        methods = [('select ', self.do_select), 
                   ('update ', self.do_update),
                   ('insert into ', self.do_insert),
                   ('delete from', self.do_delete)]
        for key, method in methods:
            if query.startswith(key):
                return method(query[len(key):])
        raise SQLException('Unknown SQL (%s)' % query)

```

此时，尚未定义用于处理SQL语句的各个方法。 让我们在下一步中执行此操作。

### 选择数据

`do_select()`方法处理SQL `select`语句以从表中检索数据。

```py
class DB(DB):
    def do_select(self, query):
        FROM, WHERE = ' from ', ' where '
        table_start = query.find(FROM)
        if table_start < 0:
            raise SQLException('no table specified')

        where_start = query.find(WHERE)
        select = query[:table_start]

        if where_start >= 0:
            t_name = query[table_start + len(FROM):where_start]
            where = query[where_start + len(WHERE):]
        else:
            t_name = query[table_start + len(FROM):]
            where = ''
        _, table = self.table(t_name)

        if where:
            selected = self.expression_clause(table, "(%s)" % where)
            selected_rows = [hm for i, data, hm in selected if data]
        else:
            selected_rows = table

        rows = self.expression_clause(selected_rows, "(%s)" % select)
        return [data for i, data, hm in rows]

```

`expression_clause()`方法用于两个目的：

1.  以`select` $ x $，$ y $，$ z $ `from` $ t $的形式，*计算*（并返回）表达式$ x $，$ y $，$ z $ in 所选行的上下文。
2.  如果给出了子句`where` $ p $，它也会在行的上下文中评估$ p $，并且仅当$ p $成立时才将行包括在选择中。

为了评估$ x $，$ y $，$ z $或$ p $之类的表达式，我们使用Python评估函数。

```py
class DB(DB):
    def expression_clause(self, table, statement):
        selected = []
        for i, hm in enumerate(table):
            selected.append((i, self.my_eval(statement, {}, hm), hm))

        return selected

```

内部调用`my_eval()`来评估任何给定的语句。

```py
class DB(DB):
    def my_eval(self, statement, g, l):
        try:
            return eval(statement, g, l)
        except:
            raise SQLException('Invalid WHERE (%s)' % repr(statement))

```

**注意：**在此处使用`eval()`引入了一些重要的安全问题，我们将在本章稍后讨论。

这是我们可以使用`sql()`发出查询的方法。 请注意该表仍为空。

```py
db = sample_db()
db.sql('select year from inventory')

```

```py
[]

```

```py
db = sample_db()
db.sql('select year from inventory where year == 2018')

```

```py
[]

```

### 插入数据

`do_insert()`方法处理SQL `insert`语句。

```py
class DB(DB):
    def do_insert(self, query):
        VALUES = ' values '
        table_end = query.find('(')
        t_name = query[:table_end].strip()
        names_end = query.find(')')
        decls, table = self.table(t_name)
        names = [i.strip() for i in query[table_end + 1:names_end].split(',')]

        # verify columns exist
        for k in names:
            self.column(decls, k)

        values_start = query.find(VALUES)

        if values_start < 0:
            raise SQLException('Invalid INSERT (%s)' % repr(query))

        values = [
            i.strip() for i in query[values_start + len(VALUES) + 1:-1].split(',')
        ]

        if len(names) != len(values):
            raise SQLException(
                'names(%s) != values(%s)' % (repr(names), repr(values)))

        kvs = {k: self.convert(decls[k], v) for k, v in zip(names, values)}
        table.append(kvs)

```

在SQL中，列可以包含任何受支持的数据类型。 为了确保使用最初声明的类型存储它，我们需要能够将值转换为`convert()`提供的特定类型。

```py
import [ast](https://docs.python.org/3/library/ast.html)

```

```py
class DB(DB):
    def convert(self, cast, value):
        try:
            return cast(ast.literal_eval(value))
        except:
            raise SQLException('Invalid Conversion %s(%s)' % (cast, value))

```

这是有关如何使用SQL `insert`命令的示例：

```py
db = sample_db()
db.sql('insert into inventory (year, kind, company, model) values (1997, "van", "Ford", "E350")')
db.table('inventory')

```

```py
({'year': int, 'kind': str, 'company': str, 'model': str},
 [{'year': 1997, 'kind': 'van', 'company': 'Ford', 'model': 'E350'}])

```

填充数据库后，我们还可以运行更复杂的查询：

```py
db.sql('select year + 1, kind from inventory')

```

```py
[(1998, 'van')]

```

```py
db.sql('select year, kind from inventory where year == 1997')

```

```py
[(1997, 'van')]

```

### 更新数据

同样，`do_update()`处理SQL `update`语句。

```py
class DB(DB):
    def do_update(self, query):
        SET, WHERE = ' set ', ' where '
        table_end = query.find(SET)

        if table_end < 0:
            raise SQLException('Invalid UPDATE (%s)' % repr(query))

        set_end = table_end + 5
        t_name = query[:table_end]
        decls, table = self.table(t_name)
        names_end = query.find(WHERE)

        if names_end >= 0:
            names = query[set_end:names_end]
            where = query[names_end + len(WHERE):]
        else:
            names = query[set_end:]
            where = ''

        sets = [[i.strip() for i in name.split('=')]
                for name in names.split(',')]

        # verify columns exist
        for k, v in sets:
            self.column(decls, k)

        if where:
            selected = self.expression_clause(table, "(%s)" % where)
            updated = [hm for i, d, hm in selected if d]
        else:
            updated = table

        for hm in updated:
            for k, v in sets:
                hm[k] = self.convert(decls[k], v)

        return "%d records were updated" % len(updated)

```

这是一个例子。 让我们首先用值再次填充数据库：

```py
db = sample_db()
db.sql('insert into inventory (year, kind, company, model) values (1997, "van", "Ford", "E350")')
db.sql('select year from inventory')

```

```py
[1997]

```

现在我们可以更新内容：

```py
db.sql('update inventory set year = 1998 where year == 1997')
db.sql('select year from inventory')

```

```py
[1998]

```

```py
db.table('inventory')

```

```py
({'year': int, 'kind': str, 'company': str, 'model': str},
 [{'year': 1998, 'kind': 'van', 'company': 'Ford', 'model': 'E350'}])

```

### 删除数据

最后，SQL `delete`语句由`do_delete()`处理。

```py
class DB(DB):
    def do_delete(self, query):
        WHERE = ' where '
        table_end = query.find(WHERE)
        if table_end < 0:
            raise SQLException('Invalid DELETE (%s)' % query)
        t_name = query[:table_end].strip()
        _, table = self.table(t_name)
        where = query[table_end + len(WHERE):]
        selected = self.expression_clause(table, "%s" % where)
        deleted = [i for i, d, hm in selected if d]
        for i in sorted(deleted, reverse=True):
            del table[i]
        return "%d records were deleted" % len(deleted)

```

Here is an example. Let us first fill the database again with values:

```py
db = sample_db()
db.sql('insert into inventory (year, kind, company, model) values (1997, "van", "Ford", "E350")')
db.sql('select year from inventory')

```

```py
[1997]

```

现在我们可以删除数据：

```py
db.sql('delete from inventory where company == "Ford"')

```

```py
'1 records were deleted'

```

我们的数据库现在为空：

```py
db.sql('select year from inventory')

```

```py
[]

```

### 所有方法一起

这是如何使用我们的数据库。

```py
db = DB()

```

同样，我们首先在数据库中创建一个具有正确数据类型的表。

```py
inventory_def = {'year': int, 'kind': str, 'company': str, 'model': str}
db.create_table('inventory', inventory_def)

```

这是使用我们的数据集更新表的简单便捷功能。

```py
def update_inventory(sqldb, vehicle):
    inventory_def = sqldb.db['inventory'][0]
    k, v = zip(*inventory_def.items())
    val = [repr(cast(val)) for cast, val in zip(v, vehicle.split(','))]
    sqldb.sql('insert into inventory (%s) values (%s)' % (','.join(k),
                                                          ','.join(val)))

```

```py
for V in VEHICLES:
    update_inventory(db, V)

```

现在，我们的数据库包含与`INVENTORY`表下的`VEHICLES`相同的数据集。

```py
db.db

```

```py
{'inventory': ({'year': int, 'kind': str, 'company': str, 'model': str},
  [{'year': 1997, 'kind': 'van', 'company': 'Ford', 'model': 'E350'},
   {'year': 2000, 'kind': 'car', 'company': 'Mercury', 'model': 'Cougar'},
   {'year': 1999, 'kind': 'car', 'company': 'Chevy', 'model': 'Venture'}])}

```

这是一个示例选择语句。

```py
db.sql('select year,kind from inventory')

```

```py
[(1997, 'van'), (2000, 'car'), (1999, 'car')]

```

```py
db.sql("select company,model from inventory where kind == 'car'")

```

```py
[('Mercury', 'Cougar'), ('Chevy', 'Venture')]

```

我们可以对其进行更新。

```py
db.sql("update inventory set year = 1998, company = 'Suzuki' where kind == 'van'")

```

```py
'1 records were updated'

```

```py
db.db

```

```py
{'inventory': ({'year': int, 'kind': str, 'company': str, 'model': str},
  [{'year': 1998, 'kind': 'van', 'company': 'Suzuki', 'model': 'E350'},
   {'year': 2000, 'kind': 'car', 'company': 'Mercury', 'model': 'Cougar'},
   {'year': 1999, 'kind': 'car', 'company': 'Chevy', 'model': 'Venture'}])}

```

它甚至可以即时进行数学运算！

```py
db.sql('select int(year)+10 from inventory')

```

```py
[2008, 2010, 2009]

```

在我们的表中添加新行。

```py
db.sql("insert into inventory (year, kind, company, model) values (1, 'charriot', 'Rome', 'Quadriga')")

```

```py
db.db

```

```py
{'inventory': ({'year': int, 'kind': str, 'company': str, 'model': str},
  [{'year': 1998, 'kind': 'van', 'company': 'Suzuki', 'model': 'E350'},
   {'year': 2000, 'kind': 'car', 'company': 'Mercury', 'model': 'Cougar'},
   {'year': 1999, 'kind': 'car', 'company': 'Chevy', 'model': 'Venture'},
   {'year': 1, 'kind': 'charriot', 'company': 'Rome', 'model': 'Quadriga'}])}

```

然后我们将其删除。

```py
db.sql("delete from inventory where year < 1900")

```

```py
'1 records were deleted'

```

### 模糊SQL

为了验证一切正常，让我们开始测试。 首先，我们定义语法。

```py
import [string](https://docs.python.org/3/library/string.html)

```

```py
EXPR_GRAMMAR = {
    "<start>": ["<expr>"],
    "<expr>": ["<bexpr>", "<aexpr>", "(<expr>)", "<term>"],
    "<bexpr>": [
        "<aexpr><lt><aexpr>",
        "<aexpr><gt><aexpr>",
        "<expr>==<expr>",
        "<expr>!=<expr>",
    ],
    "<aexpr>": [
        "<aexpr>+<aexpr>", "<aexpr>-<aexpr>", "<aexpr>*<aexpr>",
        "<aexpr>/<aexpr>", "<word>(<exprs>)", "<expr>"
    ],
    "<exprs>": ["<expr>,<exprs>", "<expr>"],
    "<lt>": ["<"],
    "<gt>": [">"],
    "<term>": ["<number>", "<word>"],
    "<number>": ["<integer>.<integer>", "<integer>", "-<number>"],
    "<integer>": ["<digit><integer>", "<digit>"],
    "<word>": ["<word><letter>", "<word><digit>", "<letter>"],
    "<digit>":
    list(string.digits),
    "<letter>":
    list(string.ascii_letters + '_:.')
}

```

```py
INVENTORY_GRAMMAR = dict(
    EXPR_GRAMMAR, **{
        '<start>': ['<query>'],
        '<query>': [
            'select <exprs> from <table>',
            'select <exprs> from <table> where <bexpr>',
            'insert into <table> (<names>) values (<literals>)',
            'update <table> set <assignments> where <bexpr>',
            'delete from <table> where <bexpr>',
        ],
        '<table>': ['<word>'],
        '<names>': ['<column>,<names>', '<column>'],
        '<column>': ['<word>'],
        '<literals>': ['<literal>', '<literal>,<literals>'],
        '<literal>': ['<number>', "'<chars>'"],
        '<assignments>': ['<kvp>,<assignments>', '<kvp>'],
        '<kvp>': ['<column>=<value>'],
        '<value>': ['<word>'],
        '<chars>': ['<char>', '<char><chars>'],
        '<char>':
        [i for i in string.printable if i not in "<>'\"\t\n\r\x0b\x0c\x00"
         ] + ['<lt>', '<gt>'],
    })

```

从数据库的源代码可以看出，这些函数始终检查表名是否正确。 因此，我们修改语法以选择特定的表，以便它有更好的机会深入。 我们将在后面的部分中看到如何自动完成此操作。

```py
INVENTORY_GRAMMAR_F = dict(INVENTORY_GRAMMAR, **{'<table>': ['inventory']})

```

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer

```

```py
gf = GrammarFuzzer(INVENTORY_GRAMMAR_F)
for _ in range(10):
    query = gf.fuzz()
    print(repr(query))
    try:
        res = db.sql(query)
        print(repr(res))
    except SQLException as e:
        print("> ", e)
        pass
    except:
        traceback.print_exc()
        break
    print()

```

```py
'select O6fo,-977091.1,-36.46 from inventory'
>  Invalid WHERE ('(O6fo,-977091.1,-36.46)')

'select g3 from inventory where -3.0!=V/g/b+Q*M*G'
>  Invalid WHERE ('(-3.0!=V/g/b+Q*M*G)')

'update inventory set z=a,x=F_,Q=K where p(M)<_*S'
>  Column ('z') was not found

'update inventory set R=L5pk where e*l*y-u>K+U(:)'
>  Column ('R') was not found

'select _/d*Q+H/d(k)<t+M-A+P from inventory'
>  Invalid WHERE ('(_/d*Q+H/d(k)<t+M-A+P)')

'select F5 from inventory'
>  Invalid WHERE ('(F5)')

'update inventory set jWh.=a6 where wcY(M)>IB7(i)'
>  Column ('jWh.') was not found

'update inventory set U=y where L(W<c,(U!=W))<V(((q)==m<F),O,l)'
>  Column ('U') was not found

'delete from inventory where M/b-O*h*E<H-W>e(Y)-P'
>  Invalid WHERE ('M/b-O*h*E<H-W>e(Y)-P')

'select ((kP(86)+b*S+J/Z/U+i(U))) from inventory'
>  Invalid WHERE ('(((kP(86)+b*S+J/Z/U+i(U))))')

```

模糊测试似乎并未触发任何崩溃。 但是，崩溃是我们应该担心的唯一错误吗？

## 邪恶的邪恶

在我们的实现中，我们使用`eval()`来使用Python解释器评估表达式。 这使我们能够在SQL语句中释放Python表达式的全部功能。

```py
db.sql('select year from inventory where year < 2000')

```

```py
[1998, 1999]

```

在上面的查询中，在每行的上下文中使用Python使用`expression_clause()`来评估`year < 2000`子句； 因此，`year < 2000`评估为`True`或`False`。

对于`select` ed的表达式也是如此：

```py
db.sql('select year - 1900 if year < 2000 else year - 2000 from inventory')

```

```py
[98, 0, 99]

```

这是可行的，因为`year - 1900 if year < 2000 else year - 2000`是有效的Python表达式。 （不过，这不是有效的SQL表达式。）

上面的问题是Python表达式可以执行的操作没有*限制。 如果用户尝试以下操作怎么办？*

```py
db.sql('select __import__("os").popen("pwd").read() from inventory')

```

```py
['/Users/zeller/Projects/fuzzingbook/notebooks\n',
 '/Users/zeller/Projects/fuzzingbook/notebooks\n',
 '/Users/zeller/Projects/fuzzingbook/notebooks\n']

```

上面的语句有效地从用户的文件系统中读取。 代替`os.popen("pwd").read()`，它可以执行任意的Python命令-访问数据，安装软件，运行后台进程。 这就是“ Python表达式的全部功能”再次出现在我们这里的地方。

我们想要的是允许我们的*程序*充分利用其功能； 但是，不应委托*用户*（或任何第三方）这样做。 因此，我们需要区分从程序输入的（可信）*和从用户*输入的（不可信）*。*

允许这种区分的一种方法是*动态污点分析*。 这个想法是要识别接受用户输入的功能作为*源*，*污染*通过它们输入的任何字符串，以及那些执行危险操作的功能，例如*接收器* 。 最后，我们祝福某些功能作为*污染消毒剂*。 这个想法是，来自源头的输入必须先经过消毒处理后再到达水槽。 这使我们可以使用比仅检查崩溃更强大的预言机。

## 跟踪字符串污点

一个人可以执行各种级别的污点跟踪。 最简单的方法是跟踪字符串片段起源于特定环境，并且没有经过清除异味的过程。 为此，我们只需要用`tstr`用环境标识符（*污点*）包装原始字符串，并在每个操作上产生`tstr`实例，从而产生另一个字符串片段。 属性`taint`包含一个标签，用于标识派生此实例的环境。

### 污染字符串的类

为了捕获信息流，我们需要一个新的字符串类。 这个想法是使用新的污染字符串类`tstr`作为原始`str`类的包装。 但是，`str`是*不变的*类。 因此，它在构造后不会调用其`__init__()`方法。 这意味着`str`的任何子类也不会被调用`__init__()`方法。 如果要调用初始化例程，则需要将[挂接到`__new__()`](https://docs.python.org/3/reference/datamodel.html#basic-customization) 中并返回我们自己类的实例。 我们将其与`__init__()`中的初始化代码结合在一起。

```py
class tstr(str):
    def __new__(cls, value, *args, **kw):
        return str.__new__(cls, value)

    def __init__(self, value, taint=None, **kwargs):
        self.taint = taint

```

```py
class tstr(tstr):
    def __repr__(self):
        return tstr(str.__repr__(self), taint=self.taint)

```

```py
class tstr(tstr):
    def __str__(self):
        return str.__str__(self)

```

例如，如果将`"hello"`包裹在`tstr`中，那么我们应该能够访问其污点：

```py
thello = tstr('hello', taint='LOW')

```

```py
thello.taint

```

```py
'LOW'

```

```py
repr(thello).taint

```

```py
'LOW'

```

默认情况下，当我们包装一个字符串时，它会被污染。 因此，我们还需要一种方法来清除字符串中的污点。 一种方法是简单地返回上面的`str`实例。 但是，有时可能希望从现有实例中删除污点。 这是通过`clear_taint()`完成的。 在`clear_taint()`期间，我们仅将污点设置为`None`。 该方法带有配对方法`has_taint()`，该方法检查当前是否起源于`tstr`实例。

```py
class tstr(tstr):
    def clear_taint(self):
        self.taint = None
        return self

    def has_taint(self):
        return self.taint is not None

```

### 字符串运算符

为了传播异味，我们必须扩展字符串函数，例如运算符。 我们可以一步一步完成，重载所有字符串方法和运算符。

当我们从现有的污染字符串创建新字符串时，我们传播其污染。

```py
class tstr(tstr):
    def create(self, s):
        return tstr(s, taint=self.taint)

```

`make_str_wrapper()`函数围绕现有的字符串方法创建包装器，该方法将污点附加到该方法的结果上：

```py
def make_str_wrapper(fun):
    def proxy(self, *args, **kwargs):
        res = fun(self, *args, **kwargs)
        return self.create(res)
    return proxy

```

我们对所有返回字符串的字符串方法执行此操作：

```py
for name in ['__format__', '__mod__', '__rmod__', '__getitem__', '__add__', '__mul__', '__rmul__',
             'capitalize', 'casefold', 'center', 'encode',
             'expandtabs', 'format', 'format_map', 'join', 'ljust', 'lower', 'lstrip', 'replace',
             'rjust', 'rstrip', 'strip', 'swapcase', 'title', 'translate', 'upper']:
    fun = getattr(str, name)
    setattr(tstr, name, make_str_wrapper(fun))

```

一个丢失的运算符是`+`，左侧是常规字符串，右侧是污染字符串。 Python支持`__radd__()`方法，如果在加法项的右侧使用了关联的对象，则会调用该方法。

```py
class tstr(tstr):
    def __radd__(self, s):
        return self.create(s + str(self))

```

至此，我们已经完成。 让我们创建一个带有污点`LOW`的字符串`thello`。

```py
thello = tstr('hello', taint='LOW')

```

现在，任何子字符串也将被污染：

```py
thello[0].taint

```

```py
'LOW'

```

```py
thello[1:3].taint

```

```py
'LOW'

```

字符串添加将返回带有污点的`tstr`对象：

```py
(tstr('foo', taint='HIGH') + 'bar').taint

```

```py
'HIGH'

```

我们的`__radd__()`方法可确保如果`tstr`出现在字符串添加项的右侧，该方法也可以使用：

```py
('foo' + tstr('bar', taint='HIGH')).taint

```

```py
'HIGH'

```

```py
thello += ', world'

```

```py
thello.taint

```

```py
'LOW'

```

其他运算符（例如乘法）也可以工作：

```py
(thello * 5).taint

```

```py
'LOW'

```

```py
('hw %s' % thello).taint

```

```py
'LOW'

```

```py
(tstr('hello %s', taint='HIGH') % 'world').taint

```

```py
'HIGH'

```

```py
import [string](https://docs.python.org/3/library/string.html)

```

## 跟踪不受信任的输入

那么，污染的琴弦怎么办？ 我们重新考虑`DB`示例。 我们定义了一个“更好的” `TrustedDB`，它只接受以`"TRUSTED"`污染的字符串。

```py
class TrustedDB(DB):
    def sql(self, s):
        assert isinstance(s, tstr), "Need a tainted string"
        assert s.taint == 'TRUSTED', "Need a string with trusted taint"
        return super().sql(s)

```

传递具有“未知”（即不存在）信任级别的字符串将导致`TrustedDB`失败：

```py
bdb = TrustedDB(db.db)

```

```py
from [ExpectError](ExpectError.html) import ExpectError

```

```py
with ExpectError():
    bdb.sql("select year from INVENTORY")

```

```py
Traceback (most recent call last):
  File "<ipython-input-79-65a521f9999f>", line 2, in <module>
    bdb.sql("select year from INVENTORY")
  File "<ipython-input-76-53a654b6cc10>", line 3, in sql
    assert isinstance(s, tstr), "Need a tainted string"
AssertionError: Need a tainted string (expected)

```

另外，任何用户输入最初都将被标记为`"UNTRUSTED"`作为污点。 如果我们将不可信的字符串放入我们更好的计算器中，它也会失败：

```py
bad_user_input = tstr('__import__("os").popen("ls").read()', taint='UNTRUSTED')
with ExpectError():
    bdb.sql(bad_user_input)

```

```py
Traceback (most recent call last):
  File "<ipython-input-80-82c5b2d628ed>", line 3, in <module>
    bdb.sql(bad_user_input)
  File "<ipython-input-76-53a654b6cc10>", line 4, in sql
    assert s.taint == 'TRUSTED', "Need a string with trusted taint"
AssertionError: Need a string with trusted taint (expected)

```

因此，在计算的某个地方，我们必须将“不可信”输入转换为“可信”字符串。 此过程称为*消毒*。 就我们的目的而言，一个简单的清除功能可以确保输入仅包含很少的允许字符（不包括字母或引号）； 如果是这种情况，那么输入将获得新的`"TRUSTED"`异味。 如果不是，我们将字符串转换为（不受信任的）空字符串； 其他选择是引发错误或转义或删除“不可信”字符。

```py
import [re](https://docs.python.org/3/library/re.html)

```

```py
def sanitize(user_input):
    assert isinstance(user_input, tstr)
    if re.match(
            r'^select +[-a-zA-Z0-9_, ()]+ from +[-a-zA-Z0-9_, ()]+$', user_input):
        return tstr(user_input, taint='TRUSTED')
    else:
        return tstr('', taint='UNTRUSTED')

```

```py
good_user_input = tstr("select year,model from inventory", taint='UNTRUSTED')
sanitized_input = sanitize(good_user_input)
sanitized_input

```

```py
'select year,model from inventory'

```

```py
sanitized_input.taint

```

```py
'TRUSTED'

```

```py
bdb.sql(sanitized_input)

```

```py
[(1998, 'E350'), (2000, 'Cougar'), (1999, 'Venture')]

```

现在让我们尝试我们不受信任的输入：

```py
sanitized_input = sanitize(bad_user_input)
sanitized_input

```

```py
''

```

```py
sanitized_input.taint

```

```py
'UNTRUSTED'

```

```py
with ExpectError():
    bdb.sql(sanitized_input)

```

```py
Traceback (most recent call last):
  File "<ipython-input-88-e59f9e5c9d30>", line 2, in <module>
    bdb.sql(sanitized_input)
  File "<ipython-input-76-53a654b6cc10>", line 4, in sql
    assert s.taint == 'TRUSTED', "Need a string with trusted taint"
AssertionError: Need a string with trusted taint (expected)

```

以类似的方式，我们可以防止在[中有关Web模糊测试](WebFuzzer.html)的章节中讨论的SQL和代码注入。

## 污染意识模糊

我们还可以使用污点*将模糊测试直接引导到可能会产生危险输入的语法规则。* 这里的想法是识别由我们的模糊器生成的，导致不可信执行的输入。 首先，我们定义当污染值达到危险操作时将引发的异常。

```py
class Tainted(Exception):
    def __init__(self, v):
        self.v = v

    def __str__(self):
        return 'Tainted[%s]' % self.v

```

### TaintedDB

接下来，由于`my_eval()`是`DB`类中最危险的操作，因此我们定义了一个新类`TaintedDB`，该类将覆盖`my_eval()`以在不信任字符串到达​​此部分时引发异常。

```py
class TaintedDB(DB):
    def my_eval(self, statement, g, l):
        if statement.taint != 'TRUSTED':
            raise Tainted(statement)
        try:
            return eval(statement, g, l)
        except:
            raise SQLException('Invalid SQL (%s)' % repr(statement))

```

我们初始化一个`TaintedDB`的实例

```py
tdb = TaintedDB()

```

```py
tdb.db = db.db

```

然后我们开始模糊测试。

```py
import [traceback](https://docs.python.org/3/library/traceback.html)

```

```py
for _ in range(10):
    query = gf.fuzz()
    print(repr(query))
    try:
        res = tdb.sql(tstr(query, taint='UNTRUSTED'))
        print(repr(res))
    except SQLException as e:
        pass
    except Tainted as e:
        print("> ", e)
    except:
        traceback.print_exc()
        break
    print()

```

```py
'delete from inventory where y/u-l+f/y<Y(c)/A-H*q'
>  Tainted[y/u-l+f/y<Y(c)/A-H*q]

"insert into inventory (G,Wmp,sl3hku3) values ('<','?')"

"insert into inventory (d0) values (',_G')"

'select P*Q-w/x from inventory where X<j==:==j*r-f'
>  Tainted[(X<j==:==j*r-f)]

'select a>F*i from inventory where Q/I-_+P*j>.'
>  Tainted[(Q/I-_+P*j>.)]

'select (V-i<T/g) from inventory where T/r/G<FK(m)/(i)'
>  Tainted[(T/r/G<FK(m)/(i))]

'select (((i))),_(S,_)/L-k<H(Sv,R,n,W,Y) from inventory'
>  Tainted[((((i))),_(S,_)/L-k<H(Sv,R,n,W,Y))]

'select (N==c*U/P/y),i-e/n*y,T!=w,u from inventory'
>  Tainted[((N==c*U/P/y),i-e/n*y,T!=w,u)]

'update inventory set _=B,n=v where o-p*k-J>T'

'select s from inventory where w4g4<.m(_)/_>t'
>  Tainted[(w4g4<.m(_)/_>t)]

```

可以看到，现有表上的`insert`，`update`，`select`和`delete`语句导致异味异常。 现在，我们可以专注于这些特定种类的输入。 但是，这不是我们唯一能做的。 在后面的部分中，我们将看到如何识别使用字符起源达到不良执行效果的输入的特定部分。 但是在此之前，我们将探讨污渍的其他用途。

## 防止隐私泄漏

使用污点，我们还可以确保秘密信息不会泄漏出去。 我们可以为信息不得泄漏的字符串分配特殊的污点`"SECRET"`：

```py
secrets = tstr('<Plenty of secret keys>', taint='SECRET')

```

访问`secrets`的任何子字符串都会传播异味：

```py
secrets[1:3].taint

```

```py
'SECRET'

```

考虑[中](Fuzzer.html)[的*心跳*安全性泄漏，在该章中，服务器不仅会意外地答复发送给它的用户输入，而且还会意外地答复秘密存储器。 如果答复仅由用户输入组成，则没有与之相关的异味：](Fuzzer.html)

```py
user_input = "hello"
reply = user_input

```

```py
isinstance(reply, tstr)

```

```py
False

```

但是，如果答复包含机密的任何部分，则该答复将被污染：

```py
reply = user_input + secrets[0:5]

```

```py
reply

```

```py
'hello<Plen'

```

```py
reply.taint

```

```py
'SECRET'

```

服务器的输出功能现在将确保发送回的数据不包含任何秘密信息：

```py
def send_back(s):
    assert not isinstance(s, tstr) and not s.taint == 'SECRET'
    ...

```

```py
with ExpectError():
    send_back(reply)

```

```py
Traceback (most recent call last):
  File "<ipython-input-103-e02d8e55c3ba>", line 2, in <module>
    send_back(reply)
  File "<ipython-input-102-a105f7cd1cab>", line 2, in send_back
    assert not isinstance(s, tstr) and not s.taint == 'SECRET'
AssertionError (expected)

```

### 跟踪字符来源

我们的`tstr`解决方案可帮助您识别信息泄漏–但这绝不是完整的。 如果我们实际上从[的](Fuzzer.html)[一章中采用`heartbeat()`实现，我们将看到*任何*答复都标记为`SECRET`-甚至那些不访问秘密存储器的人：](Fuzzer.html)

```py
from [Fuzzer](Fuzzer.html) import heartbeat

```

```py
reply = heartbeat('hello', 5, memory=secrets)

```

```py
reply.taint

```

```py
'SECRET'

```

为什么是这样？ 如果我们研究`heartbeat()`的实现，我们将看到它首先从（非秘密）答复和（秘密）内存中构建了一个长字符串`memory`，然后从`memory`返回第一个字符。

```py
# Store reply in memory
    memory = reply + memory[len(reply):]

```

此时，整个存储器仍被污染为`SECRET`，*，包括*来自`reply`的非秘密部分。

我们可以通过将`reply`标记为`PUBLIC`来解决该问题，但是，此异味将与`memory`的`SECRET`标签冲突。 如果我们用两个不同的污染字符串组成一个字符串会怎样？

```py
thilo = tstr("High", taint='HIGH') + tstr("Low", taint='LOW')

```

事实证明，在这种情况下，`__add__()`方法优先于`__radd__()`方法，这意味着右边的`"Low"`字符串被视为常规（无污染）字符串。

```py
thilo

```

```py
'HighLow'

```

```py
thilo.taint

```

```py
'HIGH'

```

我们可以通过特殊处理来设置`__add__()`和其他方法来处理冲突的污点。 但是，解决此冲突的方式将高度依赖于*应用程序*：

*   如果我们使用污点表示*隐私级别*，则`SECRET`隐私应优先于`PUBLIC`隐私。 因此，`SECRET`污染的字符串和`PUBLIC`污染的字符串的任何组合都应具有`SECRET`污染。

*   如果我们使用污点表示信息的*来源*，则`UNTRUSTED`来源应优先于`TRUSTED`来源。 因此，`UNTRUSTED`污染的字符串和`TRUSTED`污染的字符串的任何组合都应具有`UNTRUSTED`污染。

当然，可以实施这种冲突解决方案。 但是即使这样，它们也不会帮助我们在`heartbeat()`示例中将机密与非机密输出数据区分开。

### 跟踪单个字符

幸运的是，有一种更好，更通用的方法可以解决上述问题。 组成异味琴弦的关键是不仅要为琴弦分配异味，还要为信息的每一位实际赋味-在我们的例子中是字符。 如果每个角色都有一个污点，则新的字符组成将仅继承每个字符的污点*。 为此，我们引入了名为*来源*的第二位信息。*

可以通过将每个实例作为单独的实例（在动态起源研究中称为*颜色*）来区分各种不受信任的来源。 您将在[语法挖掘](GrammarMiner.html)的章节中看到此技术的一个实例。

在本节中，我们携带*字符级别*的起源。 也就是说，给定一个片段是由原始原始字符串的一部分产生的，则将能够分辨出该片段来自输入字符串的哪一部分。 本质上，来自原始来源的每个输入字符索引都有其自己的颜色。

更复杂的来源（例如*位图来源*）是可能的，其中单个字符可能来自多个来源的字符索引（例如*校验和*对字符串的操作）。 我们在本章中不考虑这些。

### 跟踪字符来源的类

让我们介绍一个类`ostr`，它像`tstr`一样，为每个字符串带有污点，另外，对于每个指示其来源的字符，都带有*起源*。 它是特定范围内的连续数字（默认情况下，从零开始），指示其在特定原点内的*位置*。

```py
class ostr(str):
    DEFAULT_ORIGIN = 0

    def __new__(cls, value, *args, **kw):
        return str.__new__(cls, value)

    def __init__(self, value, taint=None, origin=None, **kwargs):
        self.taint = taint

        if origin is None:
            origin = ostr.DEFAULT_ORIGIN
        if isinstance(origin, int):
            self.origin = list(range(origin, origin + len(self)))
        else:
            self.origin = origin
        assert len(self.origin) == len(self)

```

```py
class ostr(ostr):
    def create(self, s):
        return ostr(s, taint=self.taint, origin=self.origin)

```

```py
class ostr(ostr):
    UNKNOWN_ORIGIN = -1

    def __repr__(self):
        # handle escaped chars
        origin = [ostr.UNKNOWN_ORIGIN]
        for s, o in zip(str(self), self.origin):
            origin.extend([o] * (len(repr(s)) - 2))
        origin.append(ostr.UNKNOWN_ORIGIN)
        return ostr(str.__repr__(self), taint=self.taint, origin=origin)

```

```py
class ostr(ostr):
    def __str__(self):
        return str.__str__(self)

```

默认情况下，字符原点以`0`开头：

```py
thello = ostr('hello')
assert thello.origin == [0, 1, 2, 3, 4]

```

我们还可以如下指定起始原点-`6..10`

```py
tworld = ostr('world', origin=6)
assert tworld.origin == [6, 7, 8, 9, 10]

```

```py
a = ostr("hello\tworld")

```

```py
repr(a).origin

```

```py
[-1, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, -1]

```

`str()`返回一个没有原点或异味信息的`str`实例：

```py
assert type(str(thello)) == str

```

但是`repr()`保留原始字符串的来源信息：

```py
repr(thello)

```

```py
"'hello'"

```

```py
repr(thello).origin

```

```py
[-1, 0, 1, 2, 3, 4, -1]

```

与污点一样，我们可以清除原点并检查是否存在原点：

```py
class ostr(ostr):
    def clear_taint(self):
        self.taint = None
        return self

    def has_taint(self):
        return self.taint is not None

```

```py
class ostr(ostr):
    def clear_origin(self):
        self.origin = [self.UNKNOWN_ORIGIN] * len(self)
        return self

    def has_origin(self):
        return any(origin != self.UNKNOWN_ORIGIN for origin in self.origin)

```

```py
thello = ostr('Hello')
assert thello.has_origin()

```

```py
thello.clear_origin()
assert not thello.has_origin()

```

在本节的其余部分，我们将重新实现各种字符串方法，以便它们也可以跟踪起源。 如果这对您来说太麻烦了，请右跳[到下一部分](#Checking-Origins)，其中提供了许多用法示例。

#### 创建

我们需要创建包装在`ostr`对象中的新子字符串。 但是，我们还希望允许我们的子类创建自己的实例。 因此，我们再次提供了一个`create()`方法，该方法产生一个新的`ostr`实例。

```py
class ostr(ostr):
    def create(self, res, origin=None):
        return ostr(res, taint=self.taint, origin=origin)

```

```py
thello = ostr('hello', taint='HIGH')
tworld = thello.create('world', origin=6)

```

```py
tworld.origin

```

```py
[6, 7, 8, 9, 10]

```

```py
tworld.taint

```

```py
'HIGH'

```

```py
assert (thello.origin, tworld.origin) == (
    [0, 1, 2, 3, 4], [6, 7, 8, 9, 10])

```

#### 索引

在Python中，通过`__getitem__()`提供索引。 在正整数上建立索引非常简单。 但是，它还有两个额外的皱纹。 首先是，如果索引为负，则从字符串的最后一个字符之后的末尾开始计数许多字符。 也就是说，最后一个字符的索引为`-1`

```py
class ostr(ostr):
    def __getitem__(self, key):
        res = super().__getitem__(key)
        if isinstance(key, int):
            key = len(self) + key if key < 0 else key
            return self.create(res, [self.origin[key]])
        elif isinstance(key, slice):
            return self.create(res, self.origin[key])
        else:
            assert False

```

```py
hello = ostr('hello', taint='HIGH')
assert (hello[0], hello[-1]) == ('h', 'o')
hello[0].taint

```

```py
'HIGH'

```

另一个皱纹是`__getitem__()`可以接受切片。 接下来我们讨论这个。

#### 切片

Python `slice`运算符`[n:m]`依赖于对象为`iterator`。 因此，我们定义`__iter__()`方法，该方法返回自定义`iterator`。

```py
class ostr(ostr):
    def __iter__(self):
        return ostr_iterator(self)

```

`__iter__()`方法需要支持的`iterator`对象。 `iterator`用于保存当前迭代的状态，方法是保留对原始`ostr`的引用以及当前迭代的索引`_str_idx`。

```py
class ostr_iterator():
    def __init__(self, ostr):
        self._ostr = ostr
        self._str_idx = 0

    def __next__(self):
        if self._str_idx == len(self._ostr):
            raise StopIteration
        # calls ostr getitem should be ostr
        c = self._ostr[self._str_idx]
        assert isinstance(c, ostr)
        self._str_idx += 1
        return c

```

将所有这些放在一起：

```py
thw = ostr('hello world', taint='HIGH')
thw[0:5]

```

```py
'hello'

```

```py
assert thw[0:5].has_taint()
assert thw[0:5].has_origin()

```

```py
thw[0:5].taint

```

```py
'HIGH'

```

```py
thw[0:5].origin

```

```py
[0, 1, 2, 3, 4]

```

#### 分割

```py
def make_split_wrapper(fun):
    def proxy(self, *args, **kwargs):
        lst = fun(self, *args, **kwargs)
        return [self.create(elem) for elem in lst]
    return proxy

```

```py
for name in ['split', 'rsplit', 'splitlines']:
    fun = getattr(str, name)
    setattr(ostr, name, make_split_wrapper(fun))

```

```py
thello = ostr('hello world', taint='LOW')
thello == 'hello world'

```

```py
True

```

```py
thello.split()[0].taint

```

```py
'LOW'

```

（供读者练习：处理*分区*，即按子字符串分割字符串）

#### 串联

如果将两个起源的字符串连接在一起，则可能需要将起源从每个字符串转移到结果字符串的相应部分。 字符串的连接是通过覆盖`__add__()`来完成的。

```py
class ostr(ostr):
    def __add__(self, other):
        if isinstance(other, ostr):
            return self.create(str.__add__(self, other),
                               (self.origin + other.origin))
        else:
            return self.create(str.__add__(self, other),
                               (self.origin + [self.UNKNOWN_ORIGIN for i in other]))

```

```py
thello = ostr("hello")
tworld = ostr("world", origin=6)
thw = thello + tworld
assert thw.origin == [0, 1, 2, 3, 4, 6, 7, 8, 9, 10]

```

如果`ostr`与`str`连接在一起怎么办？

```py
space = "  "
th_w = thello + space + tworld
assert th_w.origin == [
    0,
    1,
    2,
    3,
    4,
    ostr.UNKNOWN_ORIGIN,
    ostr.UNKNOWN_ORIGIN,
    6,
    7,
    8,
    9,
    10]

```

这里的一个难题是，当添加`ostr`和`str`时，用户可以先放置`str`，在这种情况下，将在`str`实例上调用`__add__()`方法。 不在`ostr`实例上。 但是，Python提供了一种解决方案。 如果在`ostr`实例上定义了`__radd__()`，则将调用该方法，而不是`str.__add__()`

```py
class ostr(ostr):
    def __radd__(self, other):
        origin = other.origin if isinstance(other, ostr) else [
            self.UNKNOWN_ORIGIN for i in other]
        return self.create(str.__add__(other, self), (origin + self.origin))

```

我们测试一下：

```py
shello = "hello"
tworld = ostr("world")
thw = shello + tworld
assert thw.origin == [ostr.UNKNOWN_ORIGIN] * len(shello) + [0, 1, 2, 3, 4]

```

这些方法：`slicing`和`concatenation`足以实现产生字符串的其他字符串方法，并且不会更改其下的字符（即不区分大小写）。 因此，接下来我们来看一个辅助方法。

#### 提取原始字符串

给定特定的输入索引，方法`x()`从`ostr`中提取相应的原始部分。 为了方便起见，它与`ints`一起支持`slices`。

```py
class ostr(ostr):
    class TaintException(Exception):
        pass

    def x(self, i=0):
        if not self.origin:
            raise origin.TaintException('Invalid request idx')
        if isinstance(i, int):
            return [self[p]
                    for p in [k for k, j in enumerate(self.origin) if j == i]]
        elif isinstance(i, slice):
            r = range(i.start or 0, i.stop or len(self), i.step or 1)
            return [self[p]
                    for p in [k for k, j in enumerate(self.origin) if j in r]]

```

```py
thw = ostr('hello world', origin=100)

```

```py
assert thw.x(101) == ['e']

```

```py
assert thw.x(slice(101, 105)) == ['e', 'l', 'l', 'o']

```

#### 替换

`replace()`方法将字符串的一部分替换为另一部分。

```py
class ostr(ostr):
    def replace(self, a, b, n=None):
        old_origin = self.origin
        b_origin = b.origin if isinstance(
            b, ostr) else [self.UNKNOWN_ORIGIN] * len(b)
        mystr = str(self)
        i = 0
        while True:
            if n and i >= n:
                break
            idx = mystr.find(a)
            if idx == -1:
                break
            last = idx + len(a)
            mystr = mystr.replace(a, b, 1)
            partA, partB = old_origin[0:idx], old_origin[last:]
            old_origin = partA + b_origin + partB
            i += 1
        return self.create(mystr, old_origin)

```

```py
my_str = ostr("aa cde aa")
res = my_str.replace('aa', 'bb')
assert res, res.origin == ('bb', 'cde', 'bb',
                           [self.UNKNOWN_ORIGIN, self.UNKNOWN_ORIGIN,
                            2, 3, 4, 5, 6,
                            self.UNKNOWN_ORIGIN, self.UNKNOWN_ORIGIN])

```

```py
my_str = ostr("aa cde aa")
res = my_str.replace('aa', ostr('bb', origin=100))
assert (
    res, res.origin) == (
        ('bb cde bb'), [
            100, 101, 2, 3, 4, 5, 6, 100, 101])

```

#### 分割

本质上，我们必须重新实现拆分操作，并且按空间拆分与其他拆分略有不同。

```py
class ostr(ostr):
    def _split_helper(self, sep, splitted):
        result_list = []
        last_idx = 0
        first_idx = 0
        sep_len = len(sep)

        for s in splitted:
            last_idx = first_idx + len(s)
            item = self[first_idx:last_idx]
            result_list.append(item)
            first_idx = last_idx + sep_len
        return result_list

    def _split_space(self, splitted):
        result_list = []
        last_idx = 0
        first_idx = 0
        sep_len = 0
        for s in splitted:
            last_idx = first_idx + len(s)
            item = self[first_idx:last_idx]
            result_list.append(item)
            v = str(self[last_idx:])
            sep_len = len(v) - len(v.lstrip(' '))
            first_idx = last_idx + sep_len
        return result_list

    def rsplit(self, sep=None, maxsplit=-1):
        splitted = super().rsplit(sep, maxsplit)
        if not sep:
            return self._split_space(splitted)
        return self._split_helper(sep, splitted)

    def split(self, sep=None, maxsplit=-1):
        splitted = super().split(sep, maxsplit)
        if not sep:
            return self._split_space(splitted)
        return self._split_helper(sep, splitted)

```

```py
my_str = ostr('ab cdef ghij kl')
ab, cdef, ghij, kl = my_str.rsplit(sep=' ')
assert (ab.origin, cdef.origin, ghij.origin,
        kl.origin) == ([0, 1], [3, 4, 5, 6], [8, 9, 10, 11], [13, 14])

my_str = ostr('ab cdef ghij kl', origin=list(range(0, 15)))
ab, cdef, ghij, kl = my_str.rsplit(sep=' ')
assert(ab.origin, cdef.origin, kl.origin) == ([0, 1], [3, 4, 5, 6], [13, 14])

```

```py
my_str = ostr('ab   cdef ghij    kl', origin=100, taint='HIGH')
ab, cdef, ghij, kl = my_str.rsplit()
assert (ab.origin, cdef.origin, ghij.origin,
        kl.origin) == ([100, 101], [105, 106, 107, 108], [110, 111, 112, 113],
                       [118, 119])

my_str = ostr('ab   cdef ghij    kl', origin=list(range(0, 20)), taint='HIGH')
ab, cdef, ghij, kl = my_str.split()
assert (ab.origin, cdef.origin, kl.origin) == ([0, 1], [5, 6, 7, 8], [18, 19])
assert ab.taint == 'HIGH'

```

#### 剥离

```py
class ostr(ostr):
    def strip(self, cl=None):
        return self.lstrip(cl).rstrip(cl)

    def lstrip(self, cl=None):
        res = super().lstrip(cl)
        i = self.find(res)
        return self[i:]

    def rstrip(self, cl=None):
        res = super().rstrip(cl)
        return self[0:len(res)]

```

```py
my_str1 = ostr("  abc  ")
v = my_str1.strip()
assert v, v.origin == ('abc', [2, 3, 4])

```

```py
my_str1 = ostr("  abc  ")
v = my_str1.lstrip()
assert (v, v.origin) == ('abc  ', [2, 3, 4, 5, 6])

```

```py
my_str1 = ostr("  abc  ")
v = my_str1.rstrip()
assert (v, v.origin) == ('  abc', [0, 1, 2, 3, 4])

```

#### 展开选项卡

```py
class ostr(ostr):
    def expandtabs(self, n=8):
        parts = self.split('\t')
        res = super().expandtabs(n)
        all_parts = []
        for i, p in enumerate(parts):
            all_parts.extend(p.origin)
            if i < len(parts) - 1:
                l = len(all_parts) % n
                all_parts.extend([p.origin[-1]] * l)
        return self.create(res, all_parts)

```

```py
my_str = str("ab\tcd")
my_ostr = ostr("ab\tcd")
v1 = my_str.expandtabs(4)
v2 = my_ostr.expandtabs(4)

```

```py
assert str(v1) == str(v2)
assert (len(v1), repr(v2), v2.origin) == (6, "'ab  cd'", [0, 1, 1, 1, 3, 4])

```

```py
class ostr(ostr):
    def join(self, iterable):
        mystr = ''
        myorigin = []
        sep_origin = self.origin
        lst = list(iterable)
        for i, s in enumerate(lst):
            sorigin = s.origin if isinstance(s, ostr) else [
                self.UNKNOWN_ORIGIN] * len(s)
            myorigin.extend(sorigin)
            mystr += str(s)
            if i < len(lst) - 1:
                myorigin.extend(sep_origin)
                mystr += str(self)
        res = super().join(iterable)
        assert len(res) == len(mystr)
        return self.create(res, myorigin)

```

```py
my_str = ostr("ab cd", origin=100)
(v1, v2), v3 = my_str.split(), 'ef'
assert (v1.origin, v2.origin) == ([100, 101], [103, 104])

```

```py
v4 = ostr('').join([v2, v3, v1])
assert (
    v4, v4.origin) == (
        'cdefab', [
            103, 104, ostr.UNKNOWN_ORIGIN, ostr.UNKNOWN_ORIGIN, 100, 101])

```

```py
my_str = ostr("ab cd", origin=100)
(v1, v2), v3 = my_str.split(), 'ef'
assert (v1.origin, v2.origin) == ([100, 101], [103, 104])

```

```py
v4 = ostr(',').join([v2, v3, v1])
assert (v4, v4.origin) == ('cd,ef,ab',
                           [103, 104, 0, ostr.UNKNOWN_ORIGIN, ostr.UNKNOWN_ORIGIN, 0, 100, 101])

```

#### 乐谱

```py
class ostr(ostr):
    def partition(self, sep):
        partA, sep, partB = super().partition(sep)
        return (self.create(partA, self.origin[0:len(partA)]),
                self.create(sep,
                            self.origin[len(partA):len(partA) + len(sep)]),
                self.create(partB, self.origin[len(partA) + len(sep):]))

    def rpartition(self, sep):
        partA, sep, partB = super().rpartition(sep)
        return (self.create(partA, self.origin[0:len(partA)]),
                self.create(sep,
                            self.origin[len(partA):len(partA) + len(sep)]),
                self.create(partB, self.origin[len(partA) + len(sep):]))

```

#### 证明

```py
class ostr(ostr):
    def ljust(self, width, fillchar=' '):
        res = super().ljust(width, fillchar)
        initial = len(res) - len(self)
        if isinstance(fillchar, tstr):
            t = fillchar.x()
        else:
            t = self.UNKNOWN_ORIGIN
        return self.create(res, [t] * initial + self.origin)

```

```py
class ostr(ostr):
    def rjust(self, width, fillchar=' '):
        res = super().rjust(width, fillchar)
        final = len(res) - len(self)
        if isinstance(fillchar, tstr):
            t = fillchar.x()
        else:
            t = self.UNKNOWN_ORIGIN
        return self.create(res, self.origin + [t] * final)

```

#### mod

```py
class ostr(ostr):
    def __mod__(self, s):
        # nothing else implemented for the time being
        assert isinstance(s, str)
        s_origin = s.origin if isinstance(
            s, ostr) else [self.UNKNOWN_ORIGIN] * len(s)
        i = self.find('%s')
        assert i >= 0
        res = super().__mod__(s)
        r_origin = self.origin[:]
        r_origin[i:i + 2] = s_origin
        return self.create(res, origin=r_origin)

```

```py
class ostr(ostr):
    def __rmod__(self, s):
        # nothing else implemented for the time being
        assert isinstance(s, str)
        r_origin = s.origin if isinstance(
            s, ostr) else [self.UNKNOWN_ORIGIN] * len(s)
        i = s.find('%s')
        assert i >= 0
        res = super().__rmod__(s)
        s_origin = self.origin[:]
        r_origin[i:i + 2] = s_origin
        return self.create(res, origin=r_origin)

```

```py
a = ostr('hello %s world', origin=100)
a

```

```py
'hello %s world'

```

```py
(a % 'good').origin

```

```py
[100, 101, 102, 103, 104, 105, -1, -1, -1, -1, 108, 109, 110, 111, 112, 113]

```

```py
b = 'hello %s world'
c = ostr('bad', origin=10)
(b % c).origin

```

```py
[-1, -1, -1, -1, -1, -1, 10, 11, 12, -1, -1, -1, -1, -1, -1]

```

#### 不改变原点的字符串方法

```py
class ostr(ostr):
    def swapcase(self):
        return self.create(str(self).swapcase(), self.origin)

    def upper(self):
        return self.create(str(self).upper(), self.origin)

    def lower(self):
        return self.create(str(self).lower(), self.origin)

    def capitalize(self):
        return self.create(str(self).capitalize(), self.origin)

    def title(self):
        return self.create(str(self).title(), self.origin)

```

```py
a = ostr('aa', origin=100).upper()
a, a.origin

```

```py
('AA', [100, 101])

```

#### 通用包装器

这些操作并非严格需要，但对于跟踪很有用。

```py
def make_str_wrapper(fun):
    def proxy(*args, **kwargs):
        res = fun(*args, **kwargs)
        return res
    return proxy

```

```py
import [inspect](https://docs.python.org/3/library/inspect.html)

```

```py
import [types](https://docs.python.org/3/library/types.html)

```

```py
ostr_members = [name for name, fn in inspect.getmembers(ostr, callable)
                if isinstance(fn, types.FunctionType) and fn.__qualname__.startswith('ostr')]

for name, fn in inspect.getmembers(str, callable):
    if name not in set(['__class__', '__new__', '__str__', '__init__',
                        '__repr__', '__getattribute__']) | set(ostr_members):
        setattr(ostr, name, make_str_wrapper(fn))

```

#### 尚未翻译的方法

这些方法从其他字符串生成字符串。 但是，对于这些方法，我们都没有正确的实现方式。 因此，在我们生成正确的翻译之前，这些都被标记为危险。

```py
def make_str_abort_wrapper(fun):
    def proxy(*args, **kwargs):
        raise ostr.TaintException(
            '%s Not implemented in `ostr`' %
            fun.__name__)
    return proxy

```

```py
for name, fn in inspect.getmembers(str, callable):
    # Omitted 'splitlines' as this is needed for formatting output in
    # IPython/Jupyter
    if name in ['__format__', 'format_map', 'format',
                '__mul__', '__rmul__', 'center', 'zfill', 'decode', 'encode']:
        setattr(ostr, name, make_str_abort_wrapper(fn))

```

### 检查原点

完成所有这些操作后，我们现在有了完整的`ostr`字符串，可以在其中轻松检查每个字符的来源。

要检查一个字符串是否起源于另一个字符串，我们可以将其转换为一个集合，然后采用标准集合操作：

```py
s = ostr("hello", origin=100)
s[1]

```

```py
'e'

```

```py
s[1].origin

```

```py
[101]

```

```py
set(s[1].origin) <= set(s.origin)

```

```py
True

```

```py
t = ostr("world", origin=200)

```

```py
set(s.origin) <= set(t.origin)

```

```py
False

```

```py
u = s + t + "!"

```

```py
u.origin

```

```py
[100, 101, 102, 103, 104, 200, 201, 202, 203, 204, -1]

```

```py
ostr.UNKNOWN_ORIGIN in u.origin

```

```py
True

```

### 再谈隐私泄漏

让我们应用它来看看我们是否可以提出令人满意的解决方案来检查`heartbeat()`功能是否存在信息泄漏。

```py
SECRET_ORIGIN = 1000

```

我们定义了一个绝不能泄漏的“秘密”：

```py
secret = ostr('<again, some super-secret input>', origin=SECRET_ORIGIN)

```

`secret`中的每个字符都有一个以`SECRET_ORIGIN`开头的原点：

```py
print(secret.origin)

```

```py
[1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031]

```

如果现在使用给定的字符串调用`heartbeat()`，则答复的来源都应为`UNKNOWN_ORIGIN`（来自输入），并且所有字符都不应具有`SECRET_ORIGIN`。

```py
s = heartbeat('hello', 5, memory=secret)
s

```

```py
'hello'

```

```py
print(s.origin)

```

```py
[-1, -1, -1, -1, -1]

```

我们可以通过制定适当的断言来验证机密没有泄漏：

```py
assert s.origin == [ostr.UNKNOWN_ORIGIN] * len(s)

```

```py
assert all(origin == ostr.UNKNOWN_ORIGIN for origin in s.origin)

```

```py
assert not any(origin >= SECRET_ORIGIN for origin in s.origin)

```

所有断言都通过，再次确认没有秘密泄漏出去。

现在让我们去利用`heartbeat()`揭示其秘密。 由于`heartbeat()`保持不变，因此与以前一样脆弱：

```py
s = heartbeat('hello', 32, memory=secret)
s

```

```py
'hellon, some super-secret input>'

```

但是，现在，回复*确实*包含机密信息：

```py
print(s.origin)

```

```py
[-1, -1, -1, -1, -1, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031]

```

```py
with ExpectError():
    assert s.origin == [ostr.UNKNOWN_ORIGIN] * len(s)

```

```py
Traceback (most recent call last):
  File "<ipython-input-203-6cac9e5bbde7>", line 2, in <module>
    assert s.origin == [ostr.UNKNOWN_ORIGIN] * len(s)
AssertionError (expected)

```

```py
with ExpectError():
    assert all(origin == ostr.UNKNOWN_ORIGIN for origin in s.origin)

```

```py
Traceback (most recent call last):
  File "<ipython-input-204-860ea80b8867>", line 2, in <module>
    assert all(origin == ostr.UNKNOWN_ORIGIN for origin in s.origin)
AssertionError (expected)

```

```py
with ExpectError():
    assert not any(origin >= SECRET_ORIGIN for origin in s.origin)

```

```py
Traceback (most recent call last):
  File "<ipython-input-205-9630f3080c59>", line 2, in <module>
    assert not any(origin >= SECRET_ORIGIN for origin in s.origin)
AssertionError (expected)

```

现在，我们可以将这些断言集成到`heartbeat()`函数中，从而使其在泄漏信息之前失败。 另外（或替代？），我们还可以重写输出函数，以不泄露任何秘密信息。 我们将把这两个练习留给读者。

## 污染导向模糊

先前的*污染意识模糊*有点让人不满意，因为我们不能将重点放在导致危险操作的语法特定部分上。 我们使用`TrackingDB`修复了*污点定向模糊*的问题。

这里的想法是跟踪到达`eval`的每个字符的起源。 然后，将其追溯到生成它的语法节点，并增加再次使用这些节点的可能性。

### TrackingDB

`TrackingDB`类似于`TaintedDB`。 区别在于，如果我们发现执行已达到`my_eval`，则只需提高`Tainted`即可。

```py
class TrackingDB(TaintedDB):
    def my_eval(self, statement, g, l):
        if statement.origin:
            raise Tainted(statement)
        try:
            return eval(statement, g, l)
        except:
            raise SQLException('Invalid SQL (%s)' % repr(statement))

```

接下来，我们需要一个保留污渍的特制模糊器。

### TaintedGrammarFuzzer

我们定义一个`TaintedGrammarFuzzer`类，以确保污点传播到派生树。 类似于[一章中关于语法模糊测试](GrammarFuzzer.html)的`GrammarFuzzer`，除了保留了来源和污点。

```py
import [random](https://docs.python.org/3/library/random.html)

```

```py
from [Grammars](Grammars.html) import START_SYMBOL

```

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer

```

```py
from [Parser](Parser.html) import canonical

```

```py
class TaintedGrammarFuzzer(GrammarFuzzer):
    def __init__(self,
                 grammar,
                 start_symbol=START_SYMBOL,
                 expansion_switch=1,
                 log=False):
        self.tainted_start_symbol = ostr(
            start_symbol, origin=[1] * len(start_symbol))
        self.expansion_switch = expansion_switch
        self.log = log
        self.grammar = grammar
        self.c_grammar = canonical(grammar)
        self.init_tainted_grammar()

    def expansion_cost(self, expansion, seen=set()):
        symbols = [e for e in expansion if e in self.c_grammar]
        if len(symbols) == 0:
            return 1

        if any(s in seen for s in symbols):
            return float('inf')

        return sum(self.symbol_cost(s, seen) for s in symbols) + 1

    def fuzz_tree(self):
        tree = (self.tainted_start_symbol, [])
        nt_leaves = [tree]
        expansion_trials = 0
        while nt_leaves:
            idx = random.randint(0, len(nt_leaves) - 1)
            key, children = nt_leaves[idx]
            expansions = self.ct_grammar[key]
            if expansion_trials < self.expansion_switch:
                expansion = random.choice(expansions)
            else:
                costs = [self.expansion_cost(e) for e in expansions]
                m = min(costs)
                all_min = [i for i, c in enumerate(costs) if c == m]
                expansion = expansions[random.choice(all_min)]

            new_leaves = [(token, []) for token in expansion]
            new_nt_leaves = [e for e in new_leaves if e[0] in self.ct_grammar]
            children[:] = new_leaves
            nt_leaves[idx:idx + 1] = new_nt_leaves
            if self.log:
                print("%-40s" % (key + " -> " + str(expansion)))
            expansion_trials += 1
        return tree

    def fuzz(self):
        self.derivation_tree = self.fuzz_tree()
        return self.tree_to_string(self.derivation_tree)

```

我们使用专门准备的污染语法进行模糊测试。 我们标记每个单独的定义，每个单独的规则以及每个带有单独来源的标记（在检查语法后，我们在此处选择标记边界10）。 这使我们能够准确地跟踪语法中哪些部分涉及到我们感兴趣的操作。

```py
class TaintedGrammarFuzzer(TaintedGrammarFuzzer):
    def init_tainted_grammar(self):
        key_increment, alt_increment, token_increment = 1000, 100, 10
        key_origin = key_increment
        self.ct_grammar = {}
        for key, val in self.c_grammar.items():
            key_origin += key_increment
            os = []
            for v in val:
                ts = []
                key_origin += alt_increment
                for t in v:
                    nt = ostr(t, origin=key_origin)
                    key_origin += token_increment
                    ts.append(nt)
                os.append(ts)
            self.ct_grammar[key] = os

        # a use tracking grammar
        self.ctp_grammar = {}
        for key, val in self.ct_grammar.items():
            self.ctp_grammar[key] = [(v, dict(use=0)) for v in val]

```

和以前一样，我们初始化`TrackingDB`

```py
trdb = TrackingDB(db.db)

```

最后，我们需要确保在将树转换回字符串时保留污点。 为此，我们定义了`tainted_tree_to_string()`

```py
class TaintedGrammarFuzzer(TaintedGrammarFuzzer):
    def tree_to_string(self, tree):
        symbol, children, *_ = tree
        e = ostr('')
        if children:
            return e.join([self.tree_to_string(c) for c in children])
        else:
            return e if symbol in self.c_grammar else symbol

```

我们定义`update_grammar()`，它接受到达危险操作的一组起源以及用于模糊测试以更新增强语法的原始字符串的派生树。

```py
class TaintedGrammarFuzzer(TaintedGrammarFuzzer):
    def update_grammar(self, origin, dtree):
        def update_tree(dtree, origin):
            key, children = dtree
            if children:
                updated_children = [update_tree(c, origin) for c in children]
                corigin = set.union(
                    *[o for (key, children, o) in updated_children])
                corigin = corigin.union(set(key.origin))
                return (key, children, corigin)
            else:
                my_origin = set(key.origin).intersection(origin)
                return (key, [], my_origin)

        key, children, oset = update_tree(dtree, set(origin))
        for key, alts in self.ctp_grammar.items():
            for alt, o in alts:
                alt_origins = set([i for token in alt for i in token.origin])
                if alt_origins.intersection(oset):
                    o['use'] += 1

```

有了这些，我们现在就可以开始模糊起来了。

```py
def tree_type(tree):
    key, children = tree
    return (type(key), key, [tree_type(c) for c in children])

```

```py
tgf = TaintedGrammarFuzzer(INVENTORY_GRAMMAR_F)
x = None
for _ in range(10):
    qtree = tgf.fuzz_tree()
    query = tgf.tree_to_string(qtree)
    assert isinstance(query, ostr)
    try:
        print(repr(query))
        res = trdb.sql(query)
        print(repr(res))
    except SQLException as e:
        print(e)
    except Tainted as e:
        print(e)
        origin = e.args[0].origin
        tgf.update_grammar(origin, qtree)
    except:
        traceback.print_exc()
        break
    print()

```

```py
'select (g!=(9)!=((:)==2==9)!=J)==-7 from inventory'
Tainted[((g!=(9)!=((:)==2==9)!=J)==-7)]

'delete from inventory where ((c)==T)!=5==(8!=Y)!=-5'
Tainted[((c)==T)!=5==(8!=Y)!=-5]

'select (((w==(((X!=------8)))))) from inventory'
Tainted[((((w==(((X!=------8)))))))]

'delete from inventory where ((.==(-3)!=(((-3))))!=(S==(((n))==Y))!=--2!=N==-----0==--0)!=(((((R))))==((v)))!=((((((------2==Q==-8!=(q)!=(((.!=2))==J)!=(1)!=(((-4!=--5==J!=(((A==.)))))!=(((((0==(P!=((R))!=(((j)))!=7))))==O==K))==(q))==--1==((H)==(t)==s!=-6==((y))==R)!=((H))!=W==--4==(P==(u)==-0)!=O==((-5==-------2!=4!=U))!=-1==((((((R!=-6))))))!=1!=Z)))==(((I)!=((S))!=(-4==s)==(7!=(A))==(s)==p==((_)!=(C))==((w)))))))'
Tainted[((.==(-3)!=(((-3))))!=(S==(((n))==Y))!=--2!=N==-----0==--0)!=(((((R))))==((v)))!=((((((------2==Q==-8!=(q)!=(((.!=2))==J)!=(1)!=(((-4!=--5==J!=(((A==.)))))!=(((((0==(P!=((R))!=(((j)))!=7))))==O==K))==(q))==--1==((H)==(t)==s!=-6==((y))==R)!=((H))!=W==--4==(P==(u)==-0)!=O==((-5==-------2!=4!=U))!=-1==((((((R!=-6))))))!=1!=Z)))==(((I)!=((S))!=(-4==s)==(7!=(A))==(s)==p==((_)!=(C))==((w)))))))]

'delete from inventory where ((2)==T!=-1)==N==(P)==((((((6==a)))))!=8)==(3)!=((---7))'
Tainted[((2)==T!=-1)==N==(P)==((((((6==a)))))!=8)==(3)!=((---7))]

'delete from inventory where o!=2==---5==3!=t'
Tainted[o!=2==---5==3!=t]

'select (2) from inventory'
Tainted[((2))]

'select _ from inventory'
Tainted[(_)]

'select L!=(((1!=(Z)==C)!=C))==(((-0==-5==Q!=((--2!=(-0)==((0))==M)==(A))!=(X)!=e==(K==((b)))!=b==9==((((l)!=-7!=4)!=s==G))!=6==((((5==(((v==(((((((a!=d))==0!=4!=(4)==--1==(h)==-8!=(9)==-4)))))!=I!=-4))==v!=(Y==b)))==(a))!=((7)))))))==((4)) from inventory'
Tainted[(L!=(((1!=(Z)==C)!=C))==(((-0==-5==Q!=((--2!=(-0)==((0))==M)==(A))!=(X)!=e==(K==((b)))!=b==9==((((l)!=-7!=4)!=s==G))!=6==((((5==(((v==(((((((a!=d))==0!=4!=(4)==--1==(h)==-8!=(9)==-4)))))!=I!=-4))==v!=(Y==b)))==(a))!=((7)))))))==((4)))]

'delete from inventory where _==(7==(9)!=(---5)==1)==-8'
Tainted[_==(7==(9)!=(---5)==1)==-8]

```

现在，我们可以检查增强的语法，以查看每个规则使用了多少次。

```py
tgf.ctp_grammar

```

```py
{'<start>': [(['<query>'], {'use': 10})],
 '<expr>': [(['<bexpr>'], {'use': 8}),
  (['<aexpr>'], {'use': 8}),
  (['(', '<expr>', ')'], {'use': 8}),
  (['<term>'], {'use': 10})],
 '<bexpr>': [(['<aexpr>', '<lt>', '<aexpr>'], {'use': 0}),
  (['<aexpr>', '<gt>', '<aexpr>'], {'use': 0}),
  (['<expr>', '==', '<expr>'], {'use': 8}),
  (['<expr>', '!=', '<expr>'], {'use': 8})],
 '<aexpr>': [(['<aexpr>', '+', '<aexpr>'], {'use': 0}),
  (['<aexpr>', '-', '<aexpr>'], {'use': 0}),
  (['<aexpr>', '*', '<aexpr>'], {'use': 0}),
  (['<aexpr>', '/', '<aexpr>'], {'use': 0}),
  (['<word>', '(', '<exprs>', ')'], {'use': 0}),
  (['<expr>'], {'use': 8})],
 '<exprs>': [(['<expr>', ',', '<exprs>'], {'use': 0}),
  (['<expr>'], {'use': 5})],
 '<lt>': [(['<'], {'use': 0})],
 '<gt>': [(['>'], {'use': 0})],
 '<term>': [(['<number>'], {'use': 9}), (['<word>'], {'use': 9})],
 '<number>': [(['<integer>', '.', '<integer>'], {'use': 0}),
  (['<integer>'], {'use': 9}),
  (['-', '<number>'], {'use': 8})],
 '<integer>': [(['<digit>', '<integer>'], {'use': 0}),
  (['<digit>'], {'use': 9})],
 '<word>': [(['<word>', '<letter>'], {'use': 0}),
  (['<word>', '<digit>'], {'use': 0}),
  (['<letter>'], {'use': 9})],
 '<digit>': [(['0'], {'use': 2}),
  (['1'], {'use': 4}),
  (['2'], {'use': 6}),
  (['3'], {'use': 3}),
  (['4'], {'use': 2}),
  (['5'], {'use': 5}),
  (['6'], {'use': 3}),
  (['7'], {'use': 5}),
  (['8'], {'use': 6}),
  (['9'], {'use': 3})],
 '<letter>': [(['a'], {'use': 2}),
  (['b'], {'use': 1}),
  (['c'], {'use': 1}),
  (['d'], {'use': 1}),
  (['e'], {'use': 1}),
  (['f'], {'use': 0}),
  (['g'], {'use': 1}),
  (['h'], {'use': 1}),
  (['i'], {'use': 0}),
  (['j'], {'use': 1}),
  (['k'], {'use': 0}),
  (['l'], {'use': 1}),
  (['m'], {'use': 0}),
  (['n'], {'use': 1}),
  (['o'], {'use': 1}),
  (['p'], {'use': 1}),
  (['q'], {'use': 1}),
  (['r'], {'use': 0}),
  (['s'], {'use': 2}),
  (['t'], {'use': 2}),
  (['u'], {'use': 1}),
  (['v'], {'use': 2}),
  (['w'], {'use': 2}),
  (['x'], {'use': 0}),
  (['y'], {'use': 1}),
  (['z'], {'use': 0}),
  (['A'], {'use': 2}),
  (['B'], {'use': 0}),
  (['C'], {'use': 2}),
  (['D'], {'use': 0}),
  (['E'], {'use': 0}),
  (['F'], {'use': 0}),
  (['G'], {'use': 1}),
  (['H'], {'use': 1}),
  (['I'], {'use': 2}),
  (['J'], {'use': 2}),
  (['K'], {'use': 2}),
  (['L'], {'use': 1}),
  (['M'], {'use': 1}),
  (['N'], {'use': 2}),
  (['O'], {'use': 1}),
  (['P'], {'use': 2}),
  (['Q'], {'use': 2}),
  (['R'], {'use': 1}),
  (['S'], {'use': 1}),
  (['T'], {'use': 2}),
  (['U'], {'use': 1}),
  (['V'], {'use': 0}),
  (['W'], {'use': 1}),
  (['X'], {'use': 2}),
  (['Y'], {'use': 3}),
  (['Z'], {'use': 2}),
  (['_'], {'use': 3}),
  ([':'], {'use': 1}),
  (['.'], {'use': 1})],
 '<query>': [(['select ', '<exprs>', ' from ', '<table>'], {'use': 5}),
  (['select ', '<exprs>', ' from ', '<table>', ' where ', '<bexpr>'],
   {'use': 0}),
  (['insert into ',
    '<table>',
    ' (',
    '<names>',
    ') values (',
    '<literals>',
    ')'],
   {'use': 0}),
  (['update ', '<table>', ' set ', '<assignments>', ' where ', '<bexpr>'],
   {'use': 0}),
  (['delete from ', '<table>', ' where ', '<bexpr>'], {'use': 5})],
 '<table>': [(['inventory'], {'use': 0})],
 '<names>': [(['<column>', ',', '<names>'], {'use': 0}),
  (['<column>'], {'use': 0})],
 '<column>': [(['<word>'], {'use': 0})],
 '<literals>': [(['<literal>'], {'use': 0}),
  (['<literal>', ',', '<literals>'], {'use': 0})],
 '<literal>': [(['<number>'], {'use': 0}),
  (["'", '<chars>', "'"], {'use': 0})],
 '<assignments>': [(['<kvp>', ',', '<assignments>'], {'use': 0}),
  (['<kvp>'], {'use': 0})],
 '<kvp>': [(['<column>', '=', '<value>'], {'use': 0})],
 '<value>': [(['<word>'], {'use': 0})],
 '<chars>': [(['<char>'], {'use': 0}), (['<char>', '<chars>'], {'use': 0})],
 '<char>': [(['0'], {'use': 0}),
  (['1'], {'use': 0}),
  (['2'], {'use': 0}),
  (['3'], {'use': 0}),
  (['4'], {'use': 0}),
  (['5'], {'use': 0}),
  (['6'], {'use': 0}),
  (['7'], {'use': 0}),
  (['8'], {'use': 0}),
  (['9'], {'use': 0}),
  (['a'], {'use': 0}),
  (['b'], {'use': 0}),
  (['c'], {'use': 0}),
  (['d'], {'use': 0}),
  (['e'], {'use': 0}),
  (['f'], {'use': 0}),
  (['g'], {'use': 0}),
  (['h'], {'use': 0}),
  (['i'], {'use': 0}),
  (['j'], {'use': 0}),
  (['k'], {'use': 0}),
  (['l'], {'use': 0}),
  (['m'], {'use': 0}),
  (['n'], {'use': 0}),
  (['o'], {'use': 0}),
  (['p'], {'use': 0}),
  (['q'], {'use': 0}),
  (['r'], {'use': 0}),
  (['s'], {'use': 0}),
  (['t'], {'use': 0}),
  (['u'], {'use': 0}),
  (['v'], {'use': 0}),
  (['w'], {'use': 0}),
  (['x'], {'use': 0}),
  (['y'], {'use': 0}),
  (['z'], {'use': 0}),
  (['A'], {'use': 0}),
  (['B'], {'use': 0}),
  (['C'], {'use': 0}),
  (['D'], {'use': 0}),
  (['E'], {'use': 0}),
  (['F'], {'use': 0}),
  (['G'], {'use': 0}),
  (['H'], {'use': 0}),
  (['I'], {'use': 0}),
  (['J'], {'use': 0}),
  (['K'], {'use': 0}),
  (['L'], {'use': 0}),
  (['M'], {'use': 0}),
  (['N'], {'use': 0}),
  (['O'], {'use': 0}),
  (['P'], {'use': 0}),
  (['Q'], {'use': 0}),
  (['R'], {'use': 0}),
  (['S'], {'use': 0}),
  (['T'], {'use': 0}),
  (['U'], {'use': 0}),
  (['V'], {'use': 0}),
  (['W'], {'use': 0}),
  (['X'], {'use': 0}),
  (['Y'], {'use': 0}),
  (['Z'], {'use': 0}),
  (['!'], {'use': 0}),
  (['#'], {'use': 0}),
  (['$'], {'use': 0}),
  (['%'], {'use': 0}),
  (['&'], {'use': 0}),
  (['('], {'use': 0}),
  ([')'], {'use': 0}),
  (['*'], {'use': 0}),
  (['+'], {'use': 0}),
  ([','], {'use': 0}),
  (['-'], {'use': 0}),
  (['.'], {'use': 0}),
  (['/'], {'use': 0}),
  ([':'], {'use': 0}),
  ([';'], {'use': 0}),
  (['='], {'use': 0}),
  (['?'], {'use': 0}),
  (['@'], {'use': 0}),
  (['['], {'use': 0}),
  (['\\'], {'use': 0}),
  ([']'], {'use': 0}),
  (['^'], {'use': 0}),
  (['_'], {'use': 0}),
  (['`'], {'use': 0}),
  (['{'], {'use': 0}),
  (['|'], {'use': 0}),
  (['}'], {'use': 0}),
  (['~'], {'use': 0}),
  ([' '], {'use': 0}),
  (['<lt>'], {'use': 0}),
  (['<gt>'], {'use': 0})]}

```

从这里开始，想法是将注意力集中在更频繁地到达危险操作的规则上，并增加这种值的可能性。

### 污染跟踪的限制

虽然我们的框架可以检测到信息泄漏，但这绝不是完美的。 污点有多种丢失方式，因此信息仍可能泄漏出去。

#### 转换

我们仅通过*字符串*和*字符*跟踪污点和来源。 如果将它们转换为数字（或其他数据），则信息将丢失。

例如，考虑以下功能，将单个字符转换为数字并返回：

```py
def strip_all_info(s):
    t = ""
    for c in s:
        t += chr(ord(c))
    return t

```

```py
thello = ostr("Secret")
thello

```

```py
'Secret'

```

```py
thello.origin

```

```py
[0, 1, 2, 3, 4, 5]

```

污点和原点不会通过数字转换传播：

```py
thello_stripped = strip_all_info(thello)
thello_stripped

```

```py
'Secret'

```

```py
with ExpectError():
    thello_stripped.origin

```

```py
Traceback (most recent call last):
  File "<ipython-input-223-56d5157cf575>", line 2, in <module>
    thello_stripped.origin
AttributeError: 'str' object has no attribute 'origin' (expected)

```

可以通过扩展带有污点和起源的数字来解决此问题，就像我们对字符串所做的那样。 但是，在某些时候，它仍然会崩溃，因为一旦到达Python库中的内部C函数，污点就不会传播到C函数中或跨C函数传播。 （也就是说，除非有人开始为这些实现动态污点。）

#### 内部C库

如前所述，对*内部* C库的调用不会传播污点。 例如，以下内容保留了污点，

```py
hello = ostr('hello', origin=100)
world = ostr('world', origin=200)
(hello + ' ' + world).origin

```

```py
[100, 101, 102, 103, 104, -1, 200, 201, 202, 203, 204]

```

调用应等效的`join`将失败。

```py
with ExpectError():
    ''.join([hello, ' ', world]).origin

```

```py
Traceback (most recent call last):
  File "<ipython-input-225-ad148b54cc0b>", line 2, in <module>
    ''.join([hello, ' ', world]).origin
AttributeError: 'str' object has no attribute 'origin' (expected)

```

#### 隐式信息流

即使可以污染程序中的所有数据，也仍然存在中断信息流的方法-特别是通过将显式流转换为*隐式*流，或将数据流转换为*控制流*。 这是一个例子：

```py
def strip_all_info_again(s):
    t = ""
    for c in s:
        if c == 'a':
            t += 'a'
        elif c == 'b':
            t += 'b'
        elif c == 'c':
            t += 'c'
    ...

```

使用这种功能，在`s`中的字符和`t`中的字符之间没有明确的数据流； 但是，字符串将是相同的。 在处理和操纵外部输入的程序中经常发生此问题。

#### 强制污染

转换和隐式信息流都是污点和原始信息丢失的几种可能性之一。 为了解决该问题，最好的解决方案是*始终假定不受污染的字符串*中的最坏情况：

*   谈到信任时，未污染的字符串应视为*可能是不受信任的*，因此，除非经过清理，否则不要依赖。

*   关于隐私，无污染的字符串应视为*可能是秘密的*，因此不会泄漏出去。

因此，您的程序应始终具有两种异味：一种是显式信任（或秘密），另一种是显式不信任（或非秘密）。 如果在此过程中丢失了污点，则可能必须从其源中还原污点–与上述字符串方法不同。 好处是可信任的应用程序，其中的每个信息流都可以在运行时进行检查，并且可以通过自动测试快速发现违规情况。

## 经验教训

*   基于字符串和基于字符的污点允许动态跟踪从输入到系统内部并返回到输出的信息流。

*   检查污点允许在运行时发现不可信的输入和信息泄漏。

*   数据转换和隐式数据流可能会删除污点信息。 产生的未污染琴弦应被视为具有最坏的污染。

*   污点可与绒毛结合使用，以提供比仅依赖程序崩溃更可靠的错误行为指示。

## 后续步骤

污点导向模糊测试的一个更好的替代方法是使用*符号*技术，该技术考虑了被测程序的语义。 关于[流模糊](FlowFuzzer.html)的章节介绍了这些符号技术，目的是探索信息流。 随后的[符号模糊化](SymbolicFuzzer.html)章节将介绍如何充分利用符号执行来覆盖代码。 同样，基于[搜索的模糊检测](SearchBasedFuzzer.html)通常可以提供更便宜的探索策略。

## 背景

Conti等人讨论了使用本章实现的库方法对Python进行污染分析。 [ [Conti *等*，2012年。](https://doi.org/10.1007/978-3-642-27937-9_15)]。

## 练习

### 练习1：脏污数字

引入类`tint`（用于有污染的整数），该类与`tstr`一样，具有从`tint`传递到`tint`的taint属性。

#### 第1部分：创建

实现`tint`类，以设置污点：

```py
x = tint(42, taint='SECRET')
assert x.taint == 'SECRET'

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/InformationFlow.ipynb#Exercises) to work on the exercises and see solutions.

#### 第2部分：算术表达式

确保污点与算术表达式一起传递； 支持加，减，乘和除运算符。

```py
y = x + 1
assert y.taint == 'SECRET'

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/InformationFlow.ipynb#Exercises) to work on the exercises and see solutions.

#### 第3部分：将污点从整数传递到字符串

将污染的整数转换为字符串（使用`repr()`）应产生污染的字符串：

```py
s = repr(x)
assert s.taint == 'SECRET'

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/InformationFlow.ipynb#Exercises) to work on the exercises and see solutions.

#### 第4部分：将污点从整数传递到字符串

将污点对象（具有`taint`属性）转换为整数应通过该污点：

```py
password = tstr('1234', taint='NOT_EXACTLY_SECRET')
x = tint(password)
assert x == 1234
assert x.taint == 'NOT_EXACTLY_SECRET'

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/InformationFlow.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：信息流测试

生成测试以确保*最大*信息流，并尽可能传播特定的污点。 为[基于搜索的测试](SearchBasedFuzzer.html)实现适当的适应性功能，并让基于搜索的模糊器搜索解决方案。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/InformationFlow.ipynb#Exercises) to work on the exercises and see solutions.