Apache - all versions vulnerability in OLD procesors.
Apache - all versions vulnerability in OLD procesors.
I. Entry.
Vulnerability in probably all versions of apache web server, default
install (as of version 1.3.29).
II. Vulnerability details.
There are few scenarios, few calls leading to that bug.
The first call is in mod_auth, mod_auth3 and mod_auth4. As follows:
"src/modules/standard/mod_auth.c"
and
"src/modules/standard/mod_aut3.c"
and
"src/modules/standard/mod_aut4.c"
static int authenticate_basic_user(request_rec *r)
{
...
...
const char *sent_pw;
char *real_pw;
...
...
if ((res = ap_get_basic_auth_pw(r, &sent_pw)))
return res;
...
...
if (!(real_pw = get_pw(r, c->user, sec->auth_pwfile))) {
...
...
}
...
...
invalid_pw = ap_validate_password(sent_pw, real_pw);
...
...
}
request_rec structure is declared in "src/include/httpd.h".
Now look at ap_validate_password() function in "src/ap/ap_check.c":
API_EXPORT(char *) ap_validate_password(const char *passwd, const char *hash)
{
char sample[120];
...
...
/* Netscape / SHA1 ldap style strng
*/
else if (strncmp(hash, AP_SHA1PW_ID, AP_SHA1PW_IDLEN) == 0) {
ap_sha1_base64(passwd, strlen(passwd), sample);
}
...
...
}
AP_SHA1PW_ID in "src/include/ap_sha1.h" is defined as:
...
...
#define AP_SHA1PW_ID "{SHA}"
...
...
Ok. So to the heart of problem. So for strncmp hash to be zero, above must be
passwd to ap_get_basic_auth_pw() function:
"src/main/http_pro.c"
API_EXPORT(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
{
...
...
}
The second argument, pw is evaluated inside ap_validate_password that is called
from inside get_pw():
"src/modules/standard/mod_auth.c"
static char *get_pw(request_rec *r, char *user, char *auth_pwfile)
{
...
...
}
Ok function ap_validate_password calls ap_sha1_base64(). Take a closer look:
"src/ap/ap_sha1.c"
API_EXPORT(void) ap_sha1_base64(const char *clear, int len, char *out)
{
...
...
AP_SHA1_CTX context;
...
...
ap_SHA1Init(&context);
ap_SHA1Update(&context, clear, len);
...
...
}
AP_SHA1_CTX:
"src/ap/ap_sha1.c"
typedef struct {
AP_LONG digest[5]; /* message digest */
AP_LONG count_lo, count_hi; /* 64-bit bit count */
AP_LONG data[16]; /* SHA data buffer */
int local; /* unprocessed amount in data */
} AP_SHA1_CTX;
ok, now ap_SHA1Init():
"src/ap/ap_sha1.c"
API_EXPORT(void) ap_SHA1Init(AP_SHA1_CTX *sha_info)
{
sha_info->digest[0] = 0x67452301L;
sha_info->digest[1] = 0xefcdab89L;
sha_info->digest[2] = 0x98badcfeL;
sha_info->digest[3] = 0x10325476L;
sha_info->digest[4] = 0xc3d2e1f0L;
sha_info->count_lo = 0L;
sha_info->count_hi = 0L;
sha_info->local = 0;
}
"src/ap/ap_sha1.c"
API_EXPORT(void) ap_SHA1Update(AP_SHA1_CTX *sha_info, const char *buf,
unsigned int count)
{
...
...
const AP_BYTE *buffer = (const AP_BYTE *) buf;
...
...
while (count >= SHA_BLOCKSIZE) {
ebcdic2ascii((AP_BYTE *)sha_info->data, buffer, SHA_BLOCKSIZE);
buffer += SHA_BLOCKSIZE;
count -= SHA_BLOCKSIZE;
maybe_byte_reverse(sha_info->data, SHA_BLOCKSIZE);
sha_transform(sha_info);
}
ebcdic2ascii((AP_BYTE *)sha_info->data, buffer, count);
...
...
}
Aha... good, while count is bigger or equal following constant:
"src/ap/ap_sha1.c"
...
...
#define SHA_BLOCKSIZE 64
...
...
Hm... ok, this get's evaluated further more in ebcdic2ascii() ?
"src/ap/ap_ebcdi.c"
API_EXPORT(void *)
ebcdic2ascii(void *dest, const void *srce, size_t count)
{
unsigned char *udest = dest;
const unsigned char *usrce = srce;
while (count-- != 0) {
*udest++ = os_toascii[*usrce++];
}
return dest;
}
Above function copies 64 bytes, structre AP_SHA1_CTX is an array of 16 elements.
Take a look at structure element declaration :
"src/include/ap_sha1.h"
typedef unsigned long AP_LONG; /* a 32-bit quantity */
This is fine, assuming that we have 32 bits CPU, and sizeof(unsigned long)
equals 4. So 4*16=64.
There is no guarantee that on some archs unsigned long is going to stay 32 bit
width. When it's
either longer or shorter (I am not sure if long can be 16 bits long, but
possibly ANSI C standart
doesn't say anythin about it's length in bits). Ie. on 64bit platforms,
depending on compiler
options, and compiler it self long can be either 64 (default) or 32 bits.
Take a look at that:
siusiak% uname -a; cat a.c; cc a.c; ./a.out
OSF1 sirppi V4.0 1229 alpha
int main(void) {
printf("%d\n",sizeof(unsigned long));
return 0;
}
8
siusiak%
When sizeof( unsigned long )!=4 it can lead to memory corruption in function
ebcdic2ascii(),
which will either copy too much, copyied in this example 32 bytes more than he
should and
that situaction do this bug! To bypass this not popular vulnerability we should
only
resolution is quite simple, SHA_BLOCKSIZE should be declared as sizeof(unsigned
long)*16.
Possibly SHA_BLOCKSIZE must stay 64 bytes long, than obviously author should
take care more
about single elements size.
III. Exploit.
I no time to have a very close look on it, so no proof of concept is
provided this time.
Thanks for attention for read this shit.
--
pi3 (pi3ki31ny) - pi3ki31ny@xxxxx (BIG thx for appelast and giejot)
http://www.pi3.int.pl
"MJINKS, swanky pijemy w czerwcu no nie? ;-)"