Arya

Fuzzingbook:Introduction to Software Testing
果然是很有没有学习之后,整个人都变得有点脑子转不过来弯,整理博客又遇上了一堆糟心的事,还好解决完毕可以开始学习了。...
扫描右侧二维码阅读全文
14
2019/01

Fuzzingbook:Introduction to Software Testing

果然是很有没有学习之后,整个人都变得有点脑子转不过来弯,整理博客又遇上了一堆糟心的事,还好解决完毕可以开始学习了。

1.软件测试

通过假设你的环境没有√x这个函数,而你又正巧需要,那么你现在就要自己写一个求√x的函数,类似于以前写的简易计算器。作者也给出了求√x的代码

def my_sqrt(x):
    """Computes the square root of x, using the Newton–Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

这是作者贴出来的求平方根的方法

那么现在问题来了,因为只是近似求值,所以接下来需要通过不断地测试用例来测试,这段代码求出来的平方根的值是不是准确的,类似于以前打算法比赛的时候,通过后来的无数测试用例,来测试你写的代码是否能在所有情况下均可AC

于是先过可以直接计算出结果的测试检测

my_sqrt(4)
#2.0,答案正确

然后又测试了√2

my_sqrt(2)
#1.414213562373095

显然也是正确的

接下来作者提供了一个debug的思路同时也是研究my_sqrt()这个函数如何运行的一种方式
即不断打印输出approx的值

添加语句

print("approx =", approx) 

之后修改为一个新的函数my_sqrt_with_log(x)
通过测试

my_sqrt_with_log(9)

得出结果
approx = None
approx = 4.5
approx = 3.25
approx = 3.0096153846153846
approx = 3.000015360039322
approx = 3.0000000000393214
3.0

那么问题来了,看一个问题不能只从正面看,也要从反面去研究,不仅可以通过正向测试开方是否正确,也可以通过√x*√x的方式来检测所求结果是否正确

my_sqrt(2) * my_sqrt(2)

1.9999999999999996
显然答案是存在舍入误差的,不过舍入的结果<1e-8,还在可接受范围之内

此时会发现一个问题,如果手动测试每个数据的正误,不仅繁杂而且相当耗时,那么就引入了自动化测试的方式,文章里还贴出了一段代码,是写的自动测试代码,我就不贴出来了。

不过作者又抛出了新的问题,在自动测试的代码中,很容易就变得冗长起来(为了AC一道题,我曾经写过比实际代码还长的测试代码,笑)

于是python的优势就显现出来了,较之C,它有更多可用的自带函数和语句,那么可以引入assert 的方式来判断我们写的代码结果是否正确

assert my_sqrt(4) == 2

当然,解决了判断正误的问题,那么舍入的误差要怎么判断,类似于my_sqrt(2)*my_sqrt(2)=1.9999999999999996这样的舍入问题,于是又引入了一个新的常量

EPSILON = 1e-8#10的-8次方
assert abs(my_sqrt(4) - 2) < EPSILON
#abs为绝对值函数

通过判断二者之差的绝对值是否小于1e-8来判断深入误差是否过大

def assertEquals(x, y, epsilon=1e-8):
    assert abs(x - y) < epsilon

当然此时你会发现

assertEquals(my_sqrt(4), 2)
assertEquals(my_sqrt(9), 3)
assertEquals(my_sqrt(100), 10)
assertEquals(my_sqrt(2) * my_sqrt(2), 2)
assertEquals(my_sqrt(3) * my_sqrt(3), 3)
assertEquals(my_sqrt(42.11) * my_sqrt(42.11), 42.11)

这些代码所算出来的结果都是正确无误的
当然这样手动测试所有的代码基本是没有尽头的
那么,继续写自动测试代码

for n in range(1, 1000):
    assertEquals(my_sqrt(n) * my_sqrt(n), n)

看到这里的时候我就开始不嫌弃在开始学安全之前的一些算法比赛的小经验,除了正确的结果以外,AC一道题目的另一个必然条件就是如何做到不超时,反正我以前经常各种超时(滑稽

前面介绍了过了,这些代码作者在Resources中都给出来了,你可以自行下载测试,作者写了一个Timer库和fuzzingbook_utils库,这里就直接贴出代码

import fuzzingbook_utils
from Timer import Timer
with Timer() as t:
    for n in range(1, 10000):
        assertEquals(my_sqrt(n) * my_sqrt(n), n)
        print(t.elapsed_time())

最后得出结果0.020418454994796775为9999个测试数据所耗时间(讲道理这行代码在我的电脑上就有点长了,0.039948642000000145,无奈)

当然不同的数据耗时不同,可以通过使用随机数据来得出不同的结果,这里只是一个测试思路,不做赘述。

那么什么是学习呢,就是一边写一边思考,程序写到这里的时候,从开发的角度看,还要考虑一点,那么就是如何过滤一些错误的输入,
例如

my_sqrt(-1)

当你去运行这行代码的时候会发现,陷入永无止境的循环,那么问题来了,为什么会这样子呢,我简单的运行了一下

>>> -1/2
-0.5
>>> (-0.5+(-1)/(-0.5))/2
0.75
>>> (0.75+(-1)/(0.75))/2
-0.29166666666666663
>>> (-0.29166666666666663+(-1)/(-0.29166666666666663))/2
1.5684523809523812

毫无疑问的是approx guess 将在正负数之间永无止尽的摇摆,陷入无限的循环中

此时我们要引入一个新的思想,就是捕获这种错误的输入,这里作者自己写了一个ExpectError库,也可以自己写捕获错误的代码。

当然提供报错是一方面,但更重要的是如何过滤这些错误输入,也就是攻击,那么就要从输入上去过滤,类似于如何过滤web漏洞中的各种注入是一个道理。

这里提供了一个新的安全研究思路,站在攻击者的角度去考虑如何去注入,然后去写避免这些注入的代码

比如求平方的时候,负数,字符都是需要你去避免的错误输入,我们需要去考虑到各种可能存在的边缘输入,并且过滤

def sqrt_program(arg):
    try:
        x = float(arg)
    except ValueError:
        print("Illegal Input")
    else:
        if x < 0:
            print("Illegal Number")
        else:
            print('The root of', x, 'is', my_sqrt(x))

此时再运行

sqrt_program("4")
The root of 4.0 is 2.0

sqrt_program("-1")
Illegal Number

sqrt_program("xyzzy")
Illegal Input

当然程序写到这里也没有结束,因为我们没考虑到0的情况,在my_sqrt(x)函数中当x=0时,None!=0此时approx=guess=0/2=0,程序自然会报错,但是√0=0,因此,我们还要做一下特殊情况的判断,我见过比较多的特殊情况判断都是在递归调用中,比较典型的例子就是斐波那契数列,

那么,最后的完善代码如下

def sqrt_program(arg):
    try:
        x = float(arg)
    except ValueError:
        print("Illegal Input")
    else:
        if x <0:
            print("Illegal Number")
       else :  
            if x == 0:       
                print('The root of', 0, 'is', 0)
            print('The root of', x, 'is', my_sqrt(x))

那么到这里,这一节就算是结束了,还给出了两个习题,一个是希尔排序,一个是求方程的根,目的都是为了让我们学到完善代码的方法,也是学到如何攻击的思想。

学到了什么
[+]其实之前早就写好了笔记,但是果然完善一下会更好
[+]学习如何防御,其实和学习如何攻击的思路基本是一样的,要通过用不同的案例来测试一个系统是否存在漏洞,而且越边缘的案例越好,也就是所谓的“脑洞题”吧
[+]当然,这一切都基于你有门较为熟悉的语言,可以很快写出exp去测试,而不是你手动一个个去测试,要有“懒惰”的思想啊(滑稽

Last modification:January 15th, 2019 at 11:48 am
If you think my article is useful to you, please feel free to appreciate

Leave a Comment