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 initializationself._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 = Trueterm.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 threadself._scan_thread = NonemapDict(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 += 1term.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 commandself._trace.append(self._context)if menu is not None:self._context = menuterm.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, ConfigMenuchild.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] = valueif 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().