###### tags: `firmadyne` `security`
# FIRMADYNE--Kernal Vuln
這次要先用nmap掃firmware在不同port的service上用的是哪些kernel,然後在看看這些kernel在特定版本以前,是不是有一些漏洞,可以去找一些PoC(Proof of Concept)或是CVE。
在去找了一下nmap的參數之後,我發現我可以用``-A``的參數來看看不同port上的kernel是什麼版本。接下來我就用了當時官方示範的版本NWA320來試試看。firmadyne裝好了之後,就來看一下nmap之後會有什麼資訊。
```nmap -A 192.168.0.100```

```
PORT STATE SERVICE VERSION
22/tcp open ssh Dropbear sshd 0.51 (protocol 2.0)
| ssh-hostkey:
| 1024 8f:4d:9f:21:9f:fb:a1:ac:0c:4a:53:b8:43:1c:58:45 (DSA)
|_ 1040 e7:81:46:3b:1e:19:ff:f2:6b:fb:2c:f7:b1:3c:59:81 (RSA)
80/tcp open http lighttpd 1.4.18
|_http-server-header: lighttpd/1.4.18
|_http-title: Netgear
443/tcp open ssl/http lighttpd 1.4.18
|_http-server-header: lighttpd/1.4.18
|_http-title: Netgear
| ssl-cert: Subject: commonName=Netgear HTTPS/stateOrProvinceName=CA/countryName=US
| Not valid before: 2009-04-03T12:02:24
|_Not valid after: 2019-04-01T12:02:24
|_ssl-date: 2019-07-08T07:20:37+00:00; -1s from scanner time.
| sslv2:
| SSLv2 supported
| ciphers:
| SSL2_DES_192_EDE3_CBC_WITH_MD5
| SSL2_RC2_CBC_128_CBC_WITH_MD5
| SSL2_RC4_128_WITH_MD5
| SSL2_DES_64_CBC_WITH_MD5
| SSL2_RC2_CBC_128_CBC_WITH_MD5
|_ SSL2_RC4_128_EXPORT40_WITH_MD5
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
```
| PORT | SERVICE | VERSION |
| ------- | -------- | -------- |
|22 |ssh |Dropbear sshd 0.51 (protocol 2.0)|
|80 |http |lighttpd 1.4.18|
|443 |ssl/http |lighttpd 1.4.18|
## CVE-2014-2323
在看到這些服務的server是用哪裡之後,我就去google了一下這兩個version是不是有什麼漏洞,發現lighttpd這個web-server好像有很多漏洞在以前就被發現,而且1.4.18是一個超級久之前的版本…所以就有真的超多不一樣的洞被列出來,只要是在1.4.18 ~ 1.5這之間的漏洞,基本上都是存在在1.4.18這個version裡面的,那我就點進去看看囉~

我就點到1.4.18這個裡面,然後就看到有一個分數7.5,而且他說不用需要太複雜的能力跟條件,我就決定從這個下手,看看能不能在我的firmadyne上實作出來!
[漏洞說明](https://download.lighttpd.net/lighttpd/security/lighttpd_sa_2014_01.txt)
https://seclists.org/oss-sec/2014/q1/561
在上面這個網址裡面有說到,這個漏洞是結合了兩個bug 之後,就可以實作出來的。所以我們先來看一下這兩個漏洞各自是什麼~
- request_check_hostname is too lax: it allows any host names starting
with [ipv6-address] followed by anything but a colon.
這是在說``request_check_hostname``這個function太不嚴謹了,只要host_name的開頭是ipv6-address,而且在address後面的不是':',那這個check就會給過,怎麼樣都覺得不太對吧…
- mod_mysql_vhost doesn't perform any quoting; it just replaces ? in
the query string with the hostname.
這是在說``mod_mysql_vhost``這個function沒有用任何的''來防範,只是單純的把query string裡的'?'改成hostname。
綜合以上兩個問題,我自己的想法是,只要我把我的hostname設成``[::1];drop database @#$``,這樣應該就可以把某個database給刪掉了吧(? 所以就接下去看看能不能做得到
大概看了一下之後,覺得沒有什麼太大的方向,所以就想說還是先來看一下request_check_hostname的code好了。然後這個bug也已經被修好很久了,所以本來都只能看到修好之後的code,後來找了好一陣子,才找到version:1.4.18的request.c的[source code](http://git.lighttpd.net/lighttpd/lighttpd-1.x.git/tag/?h=lighttpd-1.4.18).
再去對比修好之後的code(以下只有秀出request_check_hostname這個function的部分):
http://git.lighttpd.net/lighttpd/lighttpd-1.x.git/tree/src/request.c
```c=
1 #include <sys/stat.h>
2
3 #include <limits.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <stdio.h>
7 #include <ctype.h>
8
9 #include "request.h"
10 #include "keyvalue.h"
11 #include "log.h"
12
13 static int request_check_hostname(server *srv, connection *con, buffer *host) {
14 enum { DOMAINLABEL, TOPLABEL } stage = TOPLABEL;
15 size_t i;
16 int label_len = 0;
17 size_t host_len;
18 char *colon;
19 int is_ip = -1; /* -1 don't know yet, 0 no, 1 yes */
20 int level = 0;
21
22 UNUSED(srv);
23 UNUSED(con);
24
25 /*
26 * hostport = host [ ":" port ]
27 * host = hostname | IPv4address | IPv6address
28 * hostname = *( domainlabel "." ) toplabel [ "." ]
29 * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
30 * toplabel = alpha | alpha *( alphanum | "-" ) alphanum
31 * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
32 * IPv6address = "[" ... "]"
33 * port = *digit
34 */
35
36 /* no Host: */
37 if (!host || host->used == 0) return 0;
38
39 host_len = host->used - 1;
40
41 /* IPv6 adress */
42 if (host->ptr[0] == '[') {
43 char *c = host->ptr + 1;
44 int colon_cnt = 0;
45
46 /* check portnumber */
47 for (; *c && *c != ']'; c++) {
48 if (*c == ':') {
49 if (++colon_cnt > 7) {
50 return -1;
51 }
52 } else if (!light_isxdigit(*c)) {
53 return -1;
54 }
55 }
56
57 /* missing ] */
58 if (!*c) {
59 return -1;
60 }
61
62 /* check port */
63 if (*(c+1) == ':') {
64 for (c += 2; *c; c++) {
65 if (!light_isdigit(*c)) {
66 return -1;
67 }
68 }
69 }
70 return 0;
71 }
72
73 if (NULL != (colon = memchr(host->ptr, ':', host_len))) {
74 char *c = colon + 1;
75
76 /* check portnumber */
77 for (; *c; c++) {
78 if (!light_isdigit(*c)) return -1;
79 }
80
81 /* remove the port from the host-len */
82 host_len = colon - host->ptr;
83 }
84
85 /* Host is empty */
86 if (host_len == 0) return -1;
87
88 /* if the hostname ends in a "." strip it */
89 if (host->ptr[host_len-1] == '.') host_len -= 1;
90
91 /* scan from the right and skip the \0 */
92 for (i = host_len - 1; i + 1 > 0; i--) {
93 const char c = host->ptr[i];
94
95 switch (stage) {
96 case TOPLABEL:
97 if (c == '.') {
98 /* only switch stage, if this is not the last character */
99 if (i != host_len - 1) {
100 if (label_len == 0) {
101 return -1;
102 }
103
104 /* check the first character at right of the dot */
105 if (is_ip == 0) {
106 if (!light_isalpha(host->ptr[i+1])) {
107 return -1;
108 }
109 } else if (!light_isdigit(host->ptr[i+1])) {
110 is_ip = 0;
111 } else if ('-' == host->ptr[i+1]) {
112 return -1;
113 } else {
114 /* just digits */
115 is_ip = 1;
116 }
117
118 stage = DOMAINLABEL;
119
120 label_len = 0;
121 level++;
122 } else if (i == 0) {
123 /* just a dot and nothing else is evil */
124 return -1;
125 }
126 } else if (i == 0) {
127 /* the first character of the hostname */
128 if (!light_isalpha(c)) {
129 return -1;
130 }
131 label_len++;
132 } else {
133 if (c != '-' && !light_isalnum(c)) {
134 return -1;
135 }
136 if (is_ip == -1) {
137 if (!light_isdigit(c)) is_ip = 0;
138 }
139 label_len++;
140 }
141
142 break;
143 case DOMAINLABEL:
144 if (is_ip == 1) {
145 if (c == '.') {
146 if (label_len == 0) {
147 return -1;
148 }
149
150 label_len = 0;
151 level++;
152 } else if (!light_isdigit(c)) {
153 return -1;
154 } else {
155 label_len++;
156 }
157 } else {
158 if (c == '.') {
159 if (label_len == 0) {
160 return -1;
161 }
162
163 /* c is either - or alphanum here */
164 if ('-' == host->ptr[i+1]) {
165 return -1;
166 }
167
168 label_len = 0;
169 level++;
170 } else if (i == 0) {
171 if (!light_isalnum(c)) {
172 return -1;
173 }
174 label_len++;
175 } else {
176 if (c != '-' && !light_isalnum(c)) {
177 return -1;
178 }
179 label_len++;
180 }
181 }
182
183 break;
184 }
185 }
186
187 /* a IP has to consist of 4 parts */
188 if (is_ip == 1 && level != 3) {
189 return -1;
190 }
191
192 if (label_len == 0) {
193 return -1;
194 }
195
196 return 0;
197 }
```
然後參考了這個[Log](https://download.lighttpd.net/lighttpd/security/lighttpd-1.4.34_fix_mysql_injection.patch)的說明:
```
@@ -43,7 +43,7 @@ static int request_check_hostname(server *srv, connection *con, buffer *host) {
char *c = host->ptr + 1;
int colon_cnt = 0;
- /* check portnumber */
+ /* check the address inside [...] */
for (; *c && *c != ']'; c++) {
if (*c == ':') {
if (++colon_cnt > 7) {
@@ -67,6 +67,10 @@ static int request_check_hostname(server *srv, connection *con, buffer *host) {
}
}
}
+ else if ('\0' != *(c+1)) {
+ /* only a port is allowed to follow [...] */
+ return -1;
+ }
return 0;
}
```
我先看了一下在``static int request_check_hostname(server *srv, connection *con, buffer *host)``這個function裡面改了什麼,發現他在line_67那邊多加了一個條件判斷,就是說在ipv6的address結束之後(也就是[....]之後),應該會接他的port,ex:``[1:1:1:1:1:1:1:1]:80``。
在原本的1.4.18的版本裡面,他只有判斷如果後面']'是接':'但是':'後面沒有接數字的話,就會return -1,除此之外都會return 0(也就是check true),因此就有一個bug就是我只要不在']'後面加':',那就一定會return 0。後來在version1.4.34又加了一個判斷:
``else if ('\0' != *(c+1)) {return -1;}``
也就是說如果說後面不是接':'的話,那就一定要是'\0',不然還是會return -1(false)。然後我想一定要是'\0'的原因,我覺得應該是跟mod_mysql_vhost.c裡面的改動有關係,所以就再來看看在1.4.34的時候,他在mod_mysql_vhost.c裡面是改了什麼
```c=
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <strings.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_MYSQL
#include <mysql.h>
#endif
#include "plugin.h"
#include "log.h"
#include "stat_cache.h"
#ifdef DEBUG_MOD_MYSQL_VHOST
#define DEBUG
#endif
/*
* Plugin for lighttpd to use MySQL
* for domain to directory lookups,
* i.e virtual hosts (vhosts).
*
* Optionally sets fcgi_offset and fcgi_arg
* in preparation for fcgi.c to handle
* per-user fcgi chroot jails.
*
* /ada@riksnet.se 2004-12-06
*/
#ifdef HAVE_MYSQL
typedef struct {
MYSQL *mysql;
buffer *mydb;
buffer *myuser;
buffer *mypass;
buffer *mysock;
buffer *hostname;
unsigned short port;
buffer *mysql_pre;
buffer *mysql_post;
} plugin_config;
/* global plugin data */
typedef struct {
PLUGIN_DATA;
buffer *tmp_buf;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
/* per connection plugin data */
typedef struct {
buffer *server_name;
buffer *document_root;
buffer *fcgi_arg;
unsigned fcgi_offset;
} plugin_connection_data;
/* init the plugin data */
INIT_FUNC(mod_mysql_vhost_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
p->tmp_buf = buffer_init();
return p;
}
/* cleanup the plugin data */
SERVER_FUNC(mod_mysql_vhost_cleanup) {
plugin_data *p = p_d;
UNUSED(srv);
#ifdef DEBUG
log_error_write(srv, __FILE__, __LINE__, "ss",
"mod_mysql_vhost_cleanup", p ? "yes" : "NO");
#endif
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (!s) continue;
mysql_close(s->mysql);
buffer_free(s->mydb);
buffer_free(s->myuser);
buffer_free(s->mypass);
buffer_free(s->mysock);
buffer_free(s->mysql_pre);
buffer_free(s->mysql_post);
buffer_free(s->hostname);
free(s);
}
free(p->config_storage);
}
buffer_free(p->tmp_buf);
free(p);
return HANDLER_GO_ON;
}
/* handle the plugin per connection data */
static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d)
{
plugin_data *p = p_d;
plugin_connection_data *c = con->plugin_ctx[p->id];
UNUSED(srv);
#ifdef DEBUG
log_error_write(srv, __FILE__, __LINE__, "ss",
"mod_mysql_connection_data", c ? "old" : "NEW");
#endif
if (c) return c;
c = calloc(1, sizeof(*c));
c->server_name = buffer_init();
c->document_root = buffer_init();
c->fcgi_arg = buffer_init();
c->fcgi_offset = 0;
return con->plugin_ctx[p->id] = c;
}
/* destroy the plugin per connection data */
CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) {
plugin_data *p = p_d;
plugin_connection_data *c = con->plugin_ctx[p->id];
UNUSED(srv);
#ifdef DEBUG
log_error_write(srv, __FILE__, __LINE__, "ss",
"mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO");
#endif
if (!c) return HANDLER_GO_ON;
buffer_free(c->server_name);
buffer_free(c->document_root);
buffer_free(c->fcgi_arg);
c->fcgi_offset = 0;
free(c);
con->plugin_ctx[p->id] = NULL;
return HANDLER_GO_ON;
}
/* set configuration values */
SERVER_FUNC(mod_mysql_vhost_set_defaults) {
plugin_data *p = p_d;
char *qmark;
size_t i = 0;
config_values_t cv[] = {
{ "mysql-vhost.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.user", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.pass", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.sock", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.sql", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.hostname", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER },
{ "mysql-vhost.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER },
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s;
buffer *sel;
s = calloc(1, sizeof(plugin_config));
s->mydb = buffer_init();
s->myuser = buffer_init();
s->mypass = buffer_init();
s->mysock = buffer_init();
s->hostname = buffer_init();
s->port = 0; /* default port for mysql */
sel = buffer_init();
s->mysql = NULL;
s->mysql_pre = buffer_init();
s->mysql_post = buffer_init();
cv[0].destination = s->mydb;
cv[1].destination = s->myuser;
cv[2].destination = s->mypass;
cv[3].destination = s->mysock;
cv[4].destination = sel;
cv[5].destination = s->hostname;
cv[6].destination = &(s->port);
p->config_storage[i] = s;
if (config_insert_values_global(srv,
((data_config *)srv->config_context->data[i])->value,
cv)) return HANDLER_ERROR;
s->mysql_pre = buffer_init();
s->mysql_post = buffer_init();
if (sel->used && (qmark = strchr(sel->ptr, '?'))) {
*qmark = '\0';
buffer_copy_string(s->mysql_pre, sel->ptr);
buffer_copy_string(s->mysql_post, qmark+1);
} else {
buffer_copy_string_buffer(s->mysql_pre, sel);
}
/* required:
* - username
* - database
*
* optional:
* - password, default: empty
* - socket, default: mysql default
* - hostname, if set overrides socket
* - port, default: 3306
*/
/* all have to be set */
if (!(buffer_is_empty(s->myuser) ||
buffer_is_empty(s->mydb))) {
my_bool reconnect = 1;
int fd;
if (NULL == (s->mysql = mysql_init(NULL))) {
log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
return HANDLER_ERROR;
}
#if MYSQL_VERSION_ID >= 50013
/* in mysql versions above 5.0.3 the reconnect flag is off by default */
mysql_options(s->mysql, MYSQL_OPT_RECONNECT, &reconnect);
#endif
#define FOO(x) (s->x->used ? s->x->ptr : NULL)
if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
FOO(mydb), s->port, FOO(mysock), 0)) {
log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
return HANDLER_ERROR;
}
#undef FOO
/* set close_on_exec for mysql the hard way */
/* Note: this only works as it is done during startup, */
/* otherwise we cannot be sure that mysql is fd i-1 */
if (-1 == (fd = open("/dev/null", 0))) {
close(fd);
fcntl(fd-1, F_SETFD, FD_CLOEXEC);
}
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];
PATCH(mysql_pre);
PATCH(mysql_post);
#ifdef HAVE_MYSQL
PATCH(mysql);
#endif
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) {
PATCH(mysql_pre);
PATCH(mysql_post);
}
}
if (s->mysql) {
PATCH(mysql);
}
}
return 0;
}
#undef PATCH
/* handle document root request */
CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
plugin_data *p = p_d;
plugin_connection_data *c;
stat_cache_entry *sce;
unsigned cols;
MYSQL_ROW row;
MYSQL_RES *result = NULL;
/* no host specified? */
if (!con->uri.authority->used) return HANDLER_GO_ON;
mod_mysql_vhost_patch_connection(srv, con, p);
if (!p->conf.mysql) return HANDLER_GO_ON;
/* sets up connection data if not done yet */
c = mod_mysql_vhost_connection_data(srv, con, p_d);
/* check if cached this connection */
if (c->server_name->used && /* con->uri.authority->used && */
buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON;
/* build and run SQL query */
buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
if (p->conf.mysql_post->used) {
buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
}
if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
goto ERR500;
}
result = mysql_store_result(p->conf.mysql);
cols = mysql_num_fields(result);
row = mysql_fetch_row(result);
if (!row || cols < 1) {
/* no such virtual host */
mysql_free_result(result);
return HANDLER_GO_ON;
}
/* sanity check that really is a directory */
buffer_copy_string(p->tmp_buf, row[0]);
BUFFER_APPEND_SLASH(p->tmp_buf);
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
goto ERR500;
}
if (!S_ISDIR(sce->st.st_mode)) {
log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf);
goto ERR500;
}
/* cache the data */
buffer_copy_string_buffer(c->server_name, con->uri.authority);
buffer_copy_string_buffer(c->document_root, p->tmp_buf);
/* fcgi_offset and fcgi_arg are optional */
if (cols > 1 && row[1]) {
c->fcgi_offset = atoi(row[1]);
if (cols > 2 && row[2]) {
buffer_copy_string(c->fcgi_arg, row[2]);
} else {
c->fcgi_arg->used = 0;
}
} else {
c->fcgi_offset = c->fcgi_arg->used = 0;
}
mysql_free_result(result);
/* fix virtual server and docroot */
GO_ON: buffer_copy_string_buffer(con->server_name, c->server_name);
buffer_copy_string_buffer(con->physical.doc_root, c->document_root);
#ifdef DEBUG
log_error_write(srv, __FILE__, __LINE__, "sbbdb",
result ? "NOT CACHED" : "cached",
con->server_name, con->physical.doc_root,
c->fcgi_offset, c->fcgi_arg);
#endif
return HANDLER_GO_ON;
ERR500: if (result) mysql_free_result(result);
con->http_status = 500; /* Internal Error */
return HANDLER_FINISHED;
}
/* this function is called at dlopen() time and inits the callbacks */
int mod_mysql_vhost_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("mysql_vhost");
p->init = mod_mysql_vhost_init;
p->cleanup = mod_mysql_vhost_cleanup;
p->handle_request_done = mod_mysql_vhost_handle_connection_close;
p->set_defaults = mod_mysql_vhost_set_defaults;
p->handle_docroot = mod_mysql_vhost_handle_docroot;
return 0;
}
#else
/* we don't have mysql support, this plugin does nothing */
int mod_mysql_vhost_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("mysql_vhost");
return 0;
}
#endif
```
[1.4.34_mod_mysql_vhost.c](http://git.lighttpd.net/lighttpd/lighttpd-1.x.git/tree/src/mod_mysql_vhost.c)
一樣參考了這個[Log](https://download.lighttpd.net/lighttpd/security/lighttpd-1.4.34_fix_mysql_injection.patch)的說明:
```
diff --git a/src/mod_mysql_vhost.c b/src/mod_mysql_vhost.c
index 538cfff..a02ed2c 100644
--- a/src/mod_mysql_vhost.c
+++ b/src/mod_mysql_vhost.c
@@ -339,6 +339,7 @@ CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
mod_mysql_vhost_patch_connection(srv, con, p);
if (!p->conf.mysql) return HANDLER_GO_ON;
+ if (0 == p->conf.mysql_pre->used) return HANDLER_GO_ON;
/* sets up connection data if not done yet */
c = mod_mysql_vhost_connection_data(srv, con, p_d);
@@ -350,10 +351,20 @@ CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
/* build and run SQL query */
buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
if (p->conf.mysql_post->used) {
- buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
+ /* escape the uri.authority */
+ unsigned long to_len;
+
+ /* 'to' has to be 'from_len * 2 + 1' */
+ buffer_prepare_append(p->tmp_buf, (con->uri.authority->used - 1) * 2 + 1);
+
+ to_len = mysql_real_escape_string(p->conf.mysql,
+ p->tmp_buf->ptr + p->tmp_buf->used - 1,
+ con->uri.authority->ptr, con->uri.authority->used - 1);
+ p->tmp_buf->used += to_len;
+
buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
}
- if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
+ if (mysql_real_query(p->conf.mysql, p->tmp_buf->ptr, p->tmp_buf->used - 1)) {
log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
goto ERR500;
}
```
這邊比起前面就更複雜了一點,``- buffer_append_string_buffer(p->tmp_buf, con->uri.authority);``在log裡面他有說到,要把這句刪掉,然後加了``/* escape the uri.authority */``這句註解之後大概10行的code:
```
+ /* escape the uri.authority */
+ unsigned long to_len;
+
+ /* 'to' has to be 'from_len * 2 + 1' */
+ buffer_prepare_append(p->tmp_buf, (con->uri.authority->used - 1) * 2 + 1);
+
+ to_len = mysql_real_escape_string(p->conf.mysql,
+ p->tmp_buf->ptr + p->tmp_buf->used - 1,
+ con->uri.authority->ptr, con->uri.authority->used - 1);
+ p->tmp_buf->used += to_len;
+
buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
```
然後就可以發現說,他刪掉的那行code,其實就是把``uri.authority``直接append到他的query(p->tmp_buf)裡面,這也就是為什麼在這個cve裡面說這樣會有很大的問題的原因~
然後再來看一下他在刪掉了前面的那行code之後加了是什麼
``/* escape the uri.authority */``這句註解應該就是說要轉義透過屌con->uri.authority傳過來的data,不能直接append。所以可以發現他先設了一個to_len的變數來當作最後query string的字串長度,然後把原本的con->uri.authority->used減1('\0'),然後乘2再+1('\0'),把這個值assign給p->tmp_buf當作他的長度。
之後call了``mysqlrealescapestring``這個function把回傳值assign給前面宣告的tolen,可以我不太知道他是什麼意思,沒有trace到,最後才append(p->conf.mysqlpost),雖然我還不是太了解他更改之後為什麼就可以de之前發生的bug,不過我想說先記著,如果我後來先試出來在1.4.18這個版本的洞可以用之後,再回來trace,說不定可以發現他這樣更改之後還是有一些漏洞存在,可以再來挖~
那接下來就是要看看我們要怎麼樣去set hostname,然後就可以去做injection或是path traversal的攻擊~
## CVE-2013-4559
這次也一樣要來做不一樣的route-exploit,我找的這個是Privilege escalation的,是透過一些驗證的不完全,來得到root的權限。
- lighttpd before 1.4.33 does not check the return value of the (1) setuid, (2) setgid, or (3) setgroups functions, which might cause lighttpd to run as root if it is restarted and allows remote attackers to gain privileges, as demonstrated by multiple calls to the clone function that cause setuid to fail when the user process limit is reached.
他主要是在講說,在某些function call到``setuid`` ``setgid`` ``setgroups``這三個Function的時候,因為他們並沒有去驗證他的值是不是-1,就只是直接的把他當作一個set的function來用,那這樣如果其實沒有真的set成功,他也不會知道。
可是我現在找不到上面說的那三個Function是被放在哪裡,但是我猜這三個function應該都是要有root的權限才可以執行,所以如果我在set的時候讓他crash然後再restart的話,那我應該就可以繼續用root的權限做其他我本來不能做的事情。所以就一樣先來看source code跟version1.4.33的[fix_patch](https://download.lighttpd.net/lighttpd/security/lighttpd-1.4.33_fix_setuid.patch)。
```
@@ -820,8 +820,14 @@ int main (int argc, char **argv) {
* to /etc/group
* */
if (NULL != grp) {
- setgid(grp->gr_gid);
- setgroups(0, NULL);
+ if (-1 == setgid(grp->gr_gid)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "setgid failed: ", strerror(errno));
+ return -1;
+ }
+ if (-1 == setgroups(0, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "setgroups failed: ", strerror(errno));
+ return -1;
+ }
if (srv->srvconf.username->used) {
initgroups(srv->srvconf.username->ptr, grp->gr_gid);
}
@@ -844,7 +850,10 @@ int main (int argc, char **argv) {
#ifdef HAVE_PWD_H
/* drop root privs */
if (NULL != pwd) {
- setuid(pwd->pw_uid);
+ if (-1 == setuid(pwd->pw_uid)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "setuid failed: ", strerror(errno));
+ return -1;
+ }
}
#endif
#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
```
然後這邊是下載source code的[載點](https://download.lighttpd.net/lighttpd/releases-1.4.x/),還有我找到他有改的地方。
1.4.33:

1.4.34:

所以我們可以看到在1.4.33版本的時候的樣子,是直接call沒有判斷有沒有``==-1``,然後到1.4.34之後就會去判斷,所以他會在if裡面先call,然後如果set失敗,因為會return -1,所以root的權限就不會留著。
但是我自己這樣有遇到幾個問題,就是我找不到他set那一系列的function被宣告在哪裡,我有把上樣include的header檔都開來看過,可是都沒有。

可是我有注意到有#ifdef HAVE_PWD_H裡面有include兩個header檔。
``<grp.h>`` ``<pwd.h>``可是我在下載的壓縮檔裡面沒有看到這兩個file,所以我會再追追看一下說是不是真的宣告在兩個header檔裡面。然後我會覺得在這個裡面是因為在``drop root privs``那樣的時候,他的if也是被包在#ifdef裡面,所以我覺得應該是有關係。