w3af解析

1. w3af简介

w3afis a Web Application Attack and Audit Framework.即Web应用攻击和审计框架。w3af用python编写,依赖的库主要有2类,分别如下:

<1> Core requirements:

Python 2.6

fpconst-0.7.2:用于处理IEEE 754浮点数;

nltk:自然语言处理工具包;

SOAPpy:SOAP是简单对象访问协议,是一种交换数据的协议规范,基于XML;

pyPdf:处理PDF文档,提取信息,分割/合并页面等;

Python bindings for the libxml2 library:libxml2是C库,这里是个python中间件

Python OpenSSL:实现SSL与TLS的套件,https;

json.py:json是一种轻量级的数据交换格式;

scapy:可以用来发送、嗅探、解析和伪造网络数据包;

pysvn:支持subversion操作;

python sqlite3:精简的嵌入式开源数据库,使用一个文件存储整个数据库,没有独立的维护进程,全部由应用程序进行维护,使用特别方便;

yappi:支持配置每个线程的CPU时间(https://code.google.com/p/yappi/)

<2> Graphical user interface requirements:

graphviz:可视化图表graph;

pygtk 2.0:生成GUI;

gtk 2.12:跨平台的图形工具包

2. w3af 架构

主要分3部分

<1>内核

The core, which coordinates the whole process and provides libraries for using in plugins.

<2>UI(console和GUI)The user interfaces, which allow the user to configure and start scans

<3>插件The plugins, which find links and vulnerabilities

3.黑盒测试–web应用扫描过程

<1> 识别所有的链接,表单,查询字条串参数;Identify all links, forms, query string parameters.

<2> 对于每个输入(表单),发送构造的畸形字符串,然后分析输出;Send specially crafted strings to each input and analyze the output

<3>生成报告Generate a report with the findings

4. w3af工作过程

<1> 调用crawl plugins(如web spider)寻找所有的Links,forms,query string parameters. 通过这一步骤,将创建一个form和Links映射。

<2> 调用audit plugins(比如sqli)发送畸形数据,来尽可能的触发漏洞。

<3> 通过output plugins将发现的漏洞、调试和错误信息反馈给用户。

5.源码初窥

文件:w3af/core/controllers/w3afCore.py<1> 类w3afCore:这是整个框架的核心,它调用所有的插件,处理异常,协调所有的工作,创建线程……

scan_start_hook(self)

功能:创建目录、线程和“消费者”,用来执行一次w3af扫描。

调用:在core初始化,重新启动扫描(清除前一个扫描的所有结果和状态)

start(self)功能:UI调用该方法来启动整个扫描过程

_safe_message_print(self,msg)功能:当磁盘空间耗尽,程序不能再向磁盘写入日志信息时,会引发异常,该函数就是来处理这种异常。像backtrack这种LiveCD经常出现这种异常。

worker_pool(self)功能:返回一个类Pool的实例

cleanup(self)功能:GUI 调用该方法,当一个扫描被终止,并且用户想启动一个新的扫描时,kb所有的数据被删除。

stop(self)功能:当用户停止扫描时,UI层调用。

quit(self)功能:退出s3af

pause(self)功能:对于一个扫描,暂停或者取消暂停

verify_environment(self)功能:检查用户配置的所有参数是否正确。

scan_end_hook(self)功能:对应scan_start_hook()

exploit_phase_prerequisites()pass

_home_directory(self)功能:处理和创建/管理主目录相关的工作

_tmp_directory(self)功能:创建tmp目录,存储大量资料,/tmp/w3af/<pid>/

6. 进阶计划

读了几个源码文件,感觉具体到某一个文件源码,看明白没什么困难,但是看完之后基本上没有收获,所以下一步准备修改源码再编译,希望能有所收获。






0.引言

本文深入分析了w3af_console启动过程,详细说明了函数调用关系,有助于理解w3af框架。下面是w3af_console入口函数_main()

def _main():
    _configure_output_manager()
    sys.exit(main())

1._configure_output_manager深入分析

w3af_console执行的第一个函数_configure_output_manager()分析,执行的结果是创建的console对象保存在全局变量om.out中,om.out在main()函数中会用到,如console.sh()。下面用'->'表示调用或继承关系,一步一步的分析到源头。

->(file:w3af_console)  
_configure_output_manager()  

->(file:w3af_console)  
om.out.set_output_plugins( ['console'] )  

ps: om就是out_manager module; out是类out_manager的一个全局对象,定义在\w3af\core\controllers\out_manager.py中  

->(file:\w3af\core\controllers\out_manager.py)  
set_output_plugins( ['console'] )  

->(file:\w3af\core\controllers\out_manager.py)  
_add_output_plugins( 'console')  

->(file:\w3af\core\controllers\out_manager.py)  
plugin = factory('w3af.plugins.output.' + OutputPluginName)  
plugin.set_options(self._plugin_options[OutputPluginName])  
self._output_plugin_instances.append(plugin)  

->(file:\w3af\core\controllers\misc\factory.py)  
<pre style="padding-left: 30px;" class="lang:python decode:true">factory('w3af.plugins.output.console')
        #主要过程
        __import__('w3af.plugins.output.console')
        class_name = module_name.split('.')[-1]  #class_name='console'
        module_inst = sys.modules['w3af.plugins.output.console']
        a_class = getattr(module_inst, 'console')  # a_class 现在等价于console类
        return a_class()</pre>
<strong>(到此就返回一个console对象,下面是几个类的继承关系)</strong>
->(file:\w3af\plugins\output\console.py)
class console(OutputPlugin):
...

->(file:\w3af\core\controllers\plugins\output_plugin.py)
class OutputPlugin(Plugin):
"""
This is the base class for data output, all output plugins should inherit
from it and implement the following methods :
1. debug( message, verbose )
2. information( message, verbose )
3. error( message, verbose )
4. vulnerability( message, verbose )
"""

->(file:\w3af\core\controllers\plugins\plugins.py)
class Plugin(Configurable):
...

->(file:\w3af\core\controllers\configurable.py)
class Configurable(object):
#This is mostly "an interface"

2.main函数深入分析

假定启动w3af的命令为

./w3af_console -s script_file

2.1 ConsoleUI

main()函数中首先解析script_file,生成commands_to_run[],根据commands,初始化一个ConsoleUI对象console,这是用户操作的UI. 有两个关键操作,self.handlers初始化和self.initRoot()

#_handlers是一个字典,键盘输入字符是key,函数是value 
self._handlers = {
        '\t': self._onTab,
        '\r': self._onEnter,
        term.KEY_BACKSPACE: self._onBackspace,
        term.KEY_LEFT: self._onLeft,
        term.KEY_RIGHT: self._onRight,
        term.KEY_UP: self._onUp,
        term.KEY_DOWN: self._onDown,
        '^C': self._backOrExit,
        '^D': self._backOrExit,
        '^L': self._clearScreen,
        '^W': self._delWord,
        '^H': self._onBackspace,
        '^A': self._toLineStart,
        '^E': self._toLineEnd
    }

def __initRoot(self, do_upd):
    """
    Root menu init routine.
    """
    cons_upd = ConsoleUIUpdater(force=do_upd)
    cons_upd.update()
    # Core initialization
    self._w3af = w3afCore()
    self._w3af.plugins.set_plugins(['console'], 'output') #关键
    ...
    #一直递归下去,在类w3af_core_plugins(实例是上面的self._w3af.plugins)发现调用的是
    self._set_plugin_generic(self, 'output', ['console'])
        self._plugins_names_dict['output'] = ['console']</pre>

(ps: file:\w3af\core\ui\console\console_ui.py

ConsoleUI class represents the console.It handles the keys pressed and delegate the completion and execution tasks to the current menu.)

2.2 accept_disclaimer

创建完console实例后,console调用accept_disclaimer方法,它输出w3af的免责条款,如果用户接受,就可以继续使用,否则退出。

2.3 sh

最后调用console.sh(),进行主循环,创建一个w3af shell,执行w3af的命令。

->(file:\w3af\core\ui\console\console_ui.py)删除异常处理语句等,sh()完成的主要工作如下

def sh(self,name="w3af",callback=None)
    self._context = rootMenu(name, self, self._w3af)
    self._showPrompt()
    self._active = True
    term.setRawInputMode(True)

    self._executePending()

    while self._active:#主循环,等待用户输入,解析命令及参数
        c = term.getch()
        self._handleKey(c) #根据字典self._handlers调用相关方法

2.4 rootMenu

对于sh()的第一条语句:

self._context = rootMenu(name, self, self._w3af)

初始化一个rootMenu类的实例_context,顾名思义,包括plugins,target,exploit等根菜单;rootMenu初始化的关键代码如下->(file:\w3af\core\ui\console\rootMenu.py)

class rootMenu(menu):
    def __init__(self, name, console, core, parent=None):
        menu.__init__(self, name, console, core, parent)
        self._load_help('root')

        #   At first, there is no scan thread
        self._scan_thread = None

        mapDict(self.addChild, {
            'plugins': pluginsMenu,
            'target': (ConfigMenu, self._w3af.target),
            'misc-settings': (ConfigMenu, MiscSettings()),
            'http-settings': (ConfigMenu, self._w3af.uri_opener.settings),
            'profiles': profilesMenu,
            'bug-report': bug_report_menu,
            'exploit': exploit,
            'kb': kbMenu
        })
    def mapDict(fun, dct):
        for p in dct:
            fun(p, dct[p])

    def addChild(self, name, constructor):
        if type(constructor) in (tuple, list):
            constructor, params = constructor[0], constructor[1:]
        else:
            params = []

        self._children[name] = constructor(
            name, self._console, self._w3af, self, *params)</pre>

对于字典的第一项'plugins': pluginsMenu,mapDict()执行过程如下self.addchild('plugins',pluginsMenu)-->self.children['plugins']=pluginsMenu('plugins', self.console, self._w3af, self,[])

-->(file:\w3af\core\ui\console\plugins.py)类pluginsMenuinit中首先得到plugins目录下的插件类型types(除去attack,tests,.gits的所有目录名)

types=['audit','auth','bruteforce','crawl','evasion','grep','infrastructure','mangle','output']
for t in types:
self.addChild(t, pluginsTypeMenu)
self._children['audit']=pluginsTypeMenu('audit',...)

-->(file:\w3af\core\ui\console\plugins.py)类pluginsTypeMenuinit中将plugins/plugin_types/目录下所有插件读入到plugins[]中最后保存到一个插件字典中self.plugins={}self.plugins[plugin_name]=plugin_option_num

2.5 _showPrompt

功能输出提示符[plugins...]>>>

->(file:\w3af\core\ui\console\console_ui.py)
term.write(self._context.get_path() + ">>> ")

->(file:\w3af\core\ui\console\menu.py)
get_path()

2.6 _executePending

调用executePending(),它顺序读取console初始化时的输入参数commands指令curCmd
paste(curCmd)将指令在终端stdout显示出来,同时保存至self.line[]
onEnter()依次:执行指令,初始化当前行position=0和line=[],在新一行输出">>>";
(ps:在当前类中找不到方法,去父类中找,如rootMenu中没有execute,在其父类menu中找)

->(file:\w3af\core\ui\console\console_ui.py)

def _executePending(self):
    while (self._commands):
        curCmd, self._commands = self._commands[0], self._commands[1:]
        self._paste(curCmd)
        self._onEnter()
def _paste(self, text):
    tail = self._line[self._position:]
    for c in text:
        self._line.insert(self._position, c)
        self._position += 1

    term.write(text)
    term.write(''.join(tail))
    term.moveBack(len(tail))

def _onEnter(self):
    self._execute()  #关键
    self._initPrompt()
    self._showPrompt()

def _execute(self):
    line = self._getLineStr()
    term.setRawInputMode(False)
    om.out.console('')
    if len(line) and not line.isspace():
        self._get_history().remember(self._line)

        params = self.in_raw_line_mode() and line or self._parseLine(line)
        menu = self._context.execute(params)

        if menu:
            if callable(menu):
                menu = menu()
            elif menu != self._context:
                # Remember this for the back command
                self._trace.append(self._context)
            if menu is not None:
                self._context = menu
    term.setRawInputMode(True)</pre>

从上述代码发现函数调用路线如下

self._executePending()->self._onEnter()->self._execute()->self._context.execute()   

self._context是类rootMenu的对象,但是rootMenu类中没有execute()方法,那么去其父类menu中查找。

2.7 menu.execute

(file:\w3af\core\ui\console\menu.py)

execute方法完整注释如下

#class menu
def execute(self, tokens):
    if len(tokens) == 0:
        return self  

    command, params = tokens[0], tokens[1:]
    handler = self.get_handler(command)
    # hander 可能是:
    # _cmd_back(), _cmd_exit(), _cmd_keys(), _cmd_help(), _cmd_print()
    # 子类中按需要增加了各自特殊_cmd__XXX,如ConfigMenu类中增加了_cmd_set(),_cmd_view()..
    if handler:   # 递归出口
        return handler(params)

    children = self.get_children() 
    '''
    return self._children, it's  a dict
    在menu的子类rootMenu中对self._children进行了初始化,代码见2.4 rootMenu

    '''
    if command in children:  # in children.keys()
        child = children[command]
        #child可能是一个pluginsMenu, ConfigMenu

        child.set_child_call(True)
        #set_child_call函数处理 set命令,如w3af>>> target set target http://w3af.org/

        try:
            return child.execute(params) 
            # 按上例,params = set target http://w3af.org/
            # 由于child也是继承自menu类,所以execute是同一个方法
            # 下次调用execute时在handler中找到了_cmd_set()方法
            # 直接执行_cmd_set(' target http://w3af.org/'),不再向下递归
        finally:
            child.set_child_call(False)

    raise BaseFrameworkException("Unknown command '%s'" % command)</pre>

对于上面的最终执行函数handler(),仅分析一个set命令:类ConfigMenu的_cmd_set()方法。

2.8 _cmd_set

(file: \w3af\core\ui\console\config.py)

_cmd_set()的主干如下

#去掉的异常处理等
def _cmd_set(self, params):
    name = params[0]
    value = ' '.join(params[1:])

    self._options[name].set_value(value)
    self._unsaved_options[name] = value

    if value not in self._memory[name]:
        self._memory[name].append(value)

    if self._child_call:
        self._cmd_save([])</pre>

比如命令 w3af>>> target set target http://w3af.org/

从execute()方法执行到_cmd_set()方法时

params=['target','http://w3af.org/']

即name='target', value='http://w3af.org/'

下面关键的就是self._options

2.9 w3af_core_target

(file:\w3af\core\ui\console\config.py)

class ConfigMenu(menu)
def __init__(self, name, console, w3af, parent, configurable):
self._configurable = configurable
self._options = self._configurable.get_options()

通过回滚,找到'target'插件对象创建的时刻就是在rootMenu对象创建时,去前文找mapDict()。核对参数发现当时传入的configurable是self._w3af.target(file:\w3af\core\ui\console\rootMenu.py)

->(file:\w3af\core\controllers\w3afCore.py)class w3afCore的init中有target的创建self.target = w3af_core_target()->(file:\w3af\core\controllers\core_helpers\target.py)

get_options()涉及到的细节过多,跟w3af_console主干没有太多关系,因此不再深挖下去,以后再做分析。

3. 总结

w3af启动时,首先添加console插件,配置一个output_manager全局对象om.out,这是创建ConsoleUI,与用户交互的基础。然后在main函数中创建ConsoleUI对象,执行初始化,进入最重要的环节,生成一个w3af shell,创建rootMenu,读取命令行参数script,进行相关设置,最后进行死循环while self._active,等待用户输入,解析输入,如果是正确的命令,就(有些需要调用插件)执行相应操作,至此用户才能跟w3af进行正常交互,w3af启动成功。





本文以menu切换,插件加载与启动为主线,分析w3af源代码中函数调用和各类之间的继承与组合关系,阅读之前请先有一个DFS(深度优先遍历)的准备。

一、menu切换

1.menu类继承关系

object

|—menu

—|—rootMenu

—|—pluginsMenu

——|—pluginsTypeMenu

—|—ConfigMenu

—|—profilesMenu

—|—bug_report_menu

—|—kbMenu

—|—exploit

2.w3af>>plugins执行过程

输入plugins+回车之后,指令从ConsoleUI类的sh()方法依次执行:

self._onEnter()->self._execute()->self._context.execute()

为了分析清楚类之间的关系,下文统一将self替换成类的名字,如果实例对象对分析过程有影响,会特别标出对象名字。

sef._context,在sh()中初始化时是rootMenu类的实例,顾名思义,context是一个上下文,它会随着程序的执行而变化,具体的说它会随着w3af shell提示符的变化而变化,从回车之后,一步一步深入分析下去,过程如下。

ConsoleUI._onEnter()->ConsoleUI._execute()->rootMenu.execute()

2.1 rootMenu.execute()

ConsoleUI._context就是类rootMenu的对象,rootMenu是menu类的子类,rootMenu没有重新定义execute方法,所以最后执行的是menu.execute(‘plugins’)

childrenmenu.get_children()#返回一个字典menu._children

child = children['plugins']#返回一个类pluginsMenu对象,它在rootMenu对象创建时完成的创建

reutn child.execute([])#即pluginsMenu.execute([])

由于参数为空,转向执行父类的execute函数,menu.execute([]),在该函数中,由于参数为空,终止递归,return self,此处也就是pluginsMenu对象。

2.2 ConsoleUI._execute()

返回上一层 ConsoleUI._execute(),接着执行:

menu = pluginsMenu  #这是2.1返回的一个对象,menu在此是一个临时变量

ConsoleUI._trace.append(ConsoleUI._context) #保存路径

if menu is not None:

self._context = menu  #完成上下文切换

2.3 ConsoleUI._onEnter()

再返回上一层ConsoleUI._execute(),接着执行:

ConsoleUI._initPrompt()ConsoleUI._showPrompt()#重新显示shell提示符:w3af/plugins>>>

3.w3af/plugins>>>back执行过程

输入back+回车之后,指令从ConsoleUI类的sh()方法依次执行:

ConsoleUI._onEnter()->ConsoleUI._execute()->pluginsMenu.execute()

3.1 pluginsMenu.execute()

目前ConsoleUI._context就是类pluginsMenu的对象,pluginsMenu是menu类的子类,由于back不是一个插件类型,所以最后执行的是menu.execute(‘back’),其执行过程如下:

return pluginsMenu._cmd_back()

return pluginsMenu._console.back()

return ConsoleUI.back()

return ConsoleUI._trace.pop()

从第2节能够知道_trace在尾部append的是rootMenu对象,所以此处弹出的也是rootMenu对象。

3.2 ConsoleUI._execute()

返回上一层 ConsoleUI._execute(),接着执行:

menu = rootMenu  #这是2.1返回的一个对象,menu在此是一个临时变量

if menu is not None:

self._context = menu  #完成上下文切换

3.3 ConsoleUI._onEnter()

再返回上一层ConsoleUI._execute(),接着执行:

ConsoleUI._initPrompt()ConsoleUI._showPrompt()#重新显示shell提示符:w3af>>>

二、加载插件

加载爬虫插件web spider的命令是:

w3af新版本命令:w3af/plugins>>>crawl web_spider

w3af旧版本命令:w3af/plugins>>>discovery webSpider

1.命令解析

crawl/discovery代表插件类型,w3af的插件类型就是w3af/plugins目录下的目录名(除去attack,tests),包括audit,crawl,auth,output等。它对应于类pluginsTypeMenu.

web_spider/webSpider代表具体的插件名字,位于w3af/plugins/插件类型/目录下。

2.获取所有的可用插件

从w3af_console启动,到获取每一种类型的所有插件的过程如下

1)ConsoleUI的_init_()中 self._w3af = w3afCore()

2)ConsoleUI的sh()中调用rootMenu(…,self._w3af,…)

3)rootMenu的_init_()中生成pluginsMenu对象

4)pluginsMenu的_init_()中调用w3af.plugins.get_plugin_types(),返回所有的插件类型types

5)对于每一个插件类型type,生成一个pluginsTypeMenu对象

6)pluginsTypeMenu的_init_()中调用w3af.plugins.get_plugin_list(name),返回一种插件的所有插件名字

7)对于一种插件的所有具体插件,pluginsTypeMenu的_init_()中调用self._w3af.plugins.get_plugin_inst(self._name, p).get_options()返回一个具体插件的使用选项说明

ps: w3af.plugins是类w3af_core_plugins对象

3.w3af/plugins>>>crawl web_spider

此时的上下文是pluginsMenu(参考一.3)

ConsoleUI._onEnter()->ConsoleUI._execute()->pluginsMenu.execute(['crawl','web_spider'])

3.1 pluginsMenu.execute(['crawl','web_spider'])

1)pluginsMenu.execute(['crawl','web_spider'])

2)menu.execute(['crawl','web_spider'])

commands=’crawl’

params=['web_spider']

#由上一节可知,每一个插件类型已经生成pluginsTypeMenu对象,此外查找crawl对应的pluginsTypeMenu

3)pluginsTypeMenu.execute(['web_spider'])

4)pluingsTypeMenu._enablePlugins(‘web_spider’)

5)w3afCore.w3af_core_plugins.set_plugins([...,'web_spider'],’crawl’)

6)w3af_core_plugins._set_plugins_generic(‘crawl’,’web_spider’) #加入字典_plugins_names_dict

至此,web_spider插件加载成功。

3.2 返回过程省略

三、启动插件

w3af>>start执行后,如何创建web_spider插件类实例,并启动插件呢?

1. 创建web_spider实例

首先,当前的上下文_context是rootMenu。函数调用过程如下

1)rootMenu._cmd_start()

2)rootMenu._real_start()

3)rootMenu._w3af.plugins.init_plugins()

即w3af_core_plugins.init_plugins()

4)w3af_core_plugins.plugin_factory()

5)w3af_core_plugins.create_instances()

6)w3af_core_plugins.get_plugin_inst()

w3af创建插件实例

Python

def get_plugin_inst(self, plugin_type, plugin_name):""":return: An instance of a plugin."""plugin_inst = factory('w3af.plugins.%s.%s' % (plugin_type, plugin_name))plugin_inst.set_url_opener(self._w3af_core.uri_opener)plugin_inst.set_worker_pool(self._w3af_core.worker_pool)if plugin_name in self._plugins_options[plugin_type].keys():custom_options = self._plugins_options[plugin_type][plugin_name]plugin_inst.set_options(custom_options)# This will init some plugins like mangle and outputif plugin_type == 'attack' and not self.initialized:self.init_plugins()return plugin_inst

defget_plugin_inst(self,plugin_type,plugin_name):

"""

:return: An instance of a plugin.

"""

plugin_inst=factory('w3af.plugins.%s.%s'%(plugin_type,plugin_name))

plugin_inst.set_url_opener(self._w3af_core.uri_opener)

plugin_inst.set_worker_pool(self._w3af_core.worker_pool)

ifplugin_nameinself._plugins_options[plugin_type].keys():

custom_options=self._plugins_options[plugin_type][plugin_name]

plugin_inst.set_options(custom_options)

# This will init some plugins like mangle and output

ifplugin_type=='attack'andnotself.initialized:

self.init_plugins()

returnplugin_inst

7)factory类,就是import web_spider 插件,同时保证插件中的类名字和文件名字完全相同;web_spider类的继承关系为 object–>Configurable–>plugin–>CrawlPlugin–>web_spider

2. 运行插件

1)rootMenu._cmd_start()

2)rootMenu._real_start()

3)rootMenu._w3af.start()即w3afCore.start(),具体过程暂不分析。

四、小结

本文用DFS的方式,跟踪分析了w3af_console的menu切换,插件加载与启动的过程,理清了函数调用关系,初步弄懂了各类之间的关联。遗留的问题,线程池与w3afCore.start().


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值