/* ** mod_imagefight.c -- Apache 2 ImageFight module ** sanitizing image files with anti-XSS-RFI attacks code ** <?php die; ?> ** ** Copyright(C) 2007 Cybozu Labs, Inc. All rights reserved. ** ** To play with this sample module first compile it into a ** DSO file and install it into Apache's modules directory ** by running: ** ** $ apxs -c -i mod_imagefight.c ** ** Then activate it in Apache's httpd.conf file for instance ** for the URL /imagefight in as follows: ** ** # httpd.conf ** ** LoadModule imagefight_module modules/mod_imagefight.so ** ** <Location /> ** AddOutputFilterByType ImageFight image/gif image/jpeg image/png image/bmp ** # AddOutputFilter ImageFight .gif .jpg .jpeg .png .bmp ** </Location> ** ** Changes ** 2007.08.28 Fix BMP header cleansing code ** Thanks http://www.tokumaru.org/d/20070821.html#p01 ** 2007.08.12 Fix antixss code, add </xmp> ** Thanks http://d.hatena.ne.jp/teracc/20070811#1186855598 ** 2007.08.09 Fix antixss code, add ` and ESC ( B ** Add checking width and height of images file. ** Thanks http://d.hatena.ne.jp/teracc/20070808#1186593859 ** Thanks http://d.hatena.ne.jp/hasegawayosuke/20070814/p1 ** 2007.08.08 Fix antixss code ,add </style>. ** Thanks http://www.tokumaru.org/d/20070807.html */ #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "ap_config.h" #define max_width 6000 #define max_height 6000 #define PUT_16BE(p,x) ((p)[0] = ((x)>>8)&0xff), \ ((p)[1] = (x)&0xff) #define PUT_16LE(p,x) ((p)[1] = ((x)>>8)&0xff), \ ((p)[0] = (x)&0xff) #define GET_16LE(p) (((unsigned short)((p)[1])<<8) \ |((unsigned short)((p)[0]))) #define PUT_32GE(p,x) ((p)[0] = ((x)>>24)&0xff), \ ((p)[1] = ((x)>>16)&0xff), \ ((p)[2] = ((x)>>8)&0xff), \ ((p)[3] = (x)&0xff) #define PUT_32LE(p,x) ((p)[3] = ((x)>>24)&0xff), \ ((p)[2] = ((x)>>16)&0xff), \ ((p)[1] = ((x)>>8)&0xff), \ ((p)[0] = (x)&0xff) #define GET_32BE(p) (((unsigned long)((p)[0])<<24) \ |((unsigned long)((p)[1])<<16) \ |((unsigned long)((p)[2])<<8) \ |((unsigned long)((p)[3]))) #define GET_32LE(p) (((unsigned long)((p)[3])<<24) \ |((unsigned long)((p)[2])<<16) \ |((unsigned long)((p)[1])<<8) \ |((unsigned long)((p)[0]))) #define MY_BUCKET(f,c) apr_bucket_transient_create \ (c, sizeof(c)-1, (f)->r->connection->bucket_alloc) #define MY_BUCKET_with_null(f,c) apr_bucket_transient_create \ (c, sizeof(c), (f)->r->connection->bucket_alloc) static const char antixss[] = "\x1B(B\"'`*/-->]]></xmp>\n\n" "<img src=# style=position:absolute;top:15;left:10;visibility:visible>" "<style>body{font-size:0;visibility:hidden}</style>" "<plaintext style=display:none><?php die;?>"; static const char space200[] = " " " " " " " <?php die; ?>"; static const unsigned char spacer1x1_GIF[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b }; static const unsigned char spacer1x1_PNG[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x03, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0xdb, 0xe1, 0x4f, 0xe0, 0x00, 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xf4, 0x71, 0x64, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char spacer1x1_BMP[] = { 0x42, 0x4d, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const unsigned char spacer1x1_JPEG[] = { 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x7f, 0x0f, 0xff, 0xd9 }; static unsigned long crc_table[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; static const char *imagefight_name = "ImageFight"; static int imagefight_filter_init(ap_filter_t* f) { f->ctx = (char *)"anti-XSS-RFI attacks"; return OK ; } static apr_bucket_brigade * get_brigade_image(ap_filter_t *f, apr_bucket_brigade *bb, const char *img, apr_size_t len) { apr_bucket_brigade* bb_tmp; apr_bucket *e, *e_eos; conn_rec *c = f->r->connection; bb_tmp = apr_brigade_create(f->r->pool, c->bucket_alloc); e = apr_bucket_transient_create(img, len, c->bucket_alloc); e_eos = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_HEAD(bb_tmp, e); APR_BRIGADE_INSERT_TAIL(bb_tmp, e_eos); return bb_tmp; } static int broken_image_PNG(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket_brigade* bb_img; bb_img = get_brigade_image(f, bb, spacer1x1_PNG, sizeof(spacer1x1_PNG)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "broken_image_PNG"); return ap_pass_brigade(f->next, bb_img); } static int broken_image_JPEG(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket_brigade* bb_img; bb_img = get_brigade_image(f, bb, spacer1x1_JPEG, sizeof(spacer1x1_JPEG)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "broken_image_JPEG"); return ap_pass_brigade(f->next, bb_img); } static int broken_image_BMP(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket_brigade* bb_img; bb_img = get_brigade_image(f, bb, spacer1x1_BMP, sizeof(spacer1x1_BMP)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "broken_image_BMP"); return ap_pass_brigade(f->next, bb_img); } static int broken_image_GIF(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket_brigade* bb_img; bb_img = get_brigade_image(f, bb, spacer1x1_GIF, sizeof(spacer1x1_GIF)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "broken_image_GIF"); return ap_pass_brigade(f->next, bb_img); } static apr_bucket * split_brigade_head(ap_filter_t* f, apr_bucket_brigade* bb, apr_size_t split_len, const unsigned char **s_head) { const char *s; apr_bucket *e; apr_size_t len; apr_status_t rv; char *c = NULL; *s_head = NULL; for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (APR_BUCKET_IS_EOS(e)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "APR_BUCKET_IS_EOS(e)"); return NULL; } rv = apr_bucket_read(e, &s, &len, APR_BLOCK_READ); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "len:%d", (int)len); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "apr_bucket_read != APR_SUCCESS"); return NULL; } if (len >= split_len) { if (c == NULL) { *s_head = s; break; } else { memcpy(c, s, split_len); break; } } else { if (c == NULL) { c = apr_palloc(f->r->pool, split_len); *s_head = c; } memcpy(c, s, len); c += len; split_len -= len; } } if (split_len > e->length) { return NULL; } else if (split_len == e->length) { return APR_BUCKET_NEXT(e); } else { rv = apr_bucket_split(e, split_len); if (rv == APR_ENOTIMPL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "apr_bucket_split == APR_ENOTIMPL"); return NULL; } return APR_BUCKET_NEXT(e); } } static apr_bucket * split_rewritable_bucket(ap_filter_t *f, apr_bucket_brigade* bb, apr_bucket *e, apr_size_t split_len) { const char *s; apr_bucket *start_e, *next_e, *new_e; apr_size_t len, n; apr_status_t rv; char *c, *start_c; n = split_len; c = start_c = apr_palloc(f->r->pool, split_len); if (e == NULL) { e = APR_BRIGADE_FIRST(bb); } for (start_e = e; e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (APR_BUCKET_IS_EOS(e)) { return NULL; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "APR_BUCKET_IS_EOS(e)"); } rv = apr_bucket_read(e, &s, &len, APR_BLOCK_READ); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "len:%d/n:%d/e->length:%d", (int)len, (int)n, (int)e->length); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "apr_bucket_read != APR_SUCCESS"); return NULL; } if (len >= n) { memcpy(c, s, n); break; } else { memcpy(c, s, len); c += len; n -= len; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "len:%d, n:%d, e->length:%d", (int)len, (int)n, (int)e->length); if (n > e->length) { return NULL; } else if (n < e->length) { rv = apr_bucket_split(e, n); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "apr_bucket_split(rv) == %d, APR_ENOTIMPL(%d)", rv, APR_ENOTIMPL); return NULL; } } next_e = APR_BUCKET_NEXT(e); for (e = start_e; e != next_e; e = APR_BUCKET_NEXT(e)) { APR_BUCKET_REMOVE(e); } new_e = apr_bucket_transient_create(start_c, split_len, f->r->connection->bucket_alloc); APR_BUCKET_INSERT_BEFORE(next_e, new_e); return new_e; } static unsigned long update_crc32(unsigned long crc, unsigned char *buf, apr_size_t len) { apr_size_t n; unsigned long c = crc; for (n = 0; n < len; n++) { c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); } return c; } static unsigned long calc_crc32(unsigned char *buf, apr_size_t len) { return update_crc32(0xffffffffL, buf, len) ^ 0xffffffffL; } static int imagefight_filter_PNG(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *e, *next_e, *head_e, *crc_e, *antixss_e; unsigned char *s_head, *s_crc, *s; apr_status_t rv; unsigned long crc32, width, height; unsigned char buf1[] = "\x00\x00\x00\x00tEXtX\x00"; unsigned char buf2[] = "\x00\x00\x00\x00"; e = split_rewritable_bucket(f, bb, (apr_bucket *)NULL, (apr_size_t)33); if (e == NULL) { return broken_image_PNG(f->next, bb); } s = (unsigned char *)e->data; if (!(s[0] == 0x89 && s[1] == 0x50 && s[2] == 0x4e && s[3] == 0x47 && s[4] == 0x0d && s[5] == 0x0a && s[6] == 0x1a && s[7] == 0x0a)) { return broken_image_PNG(f->next, bb); } PUT_32GE(s + 8, 13); /* IHDR data length */ width = GET_32BE(s + 16); height = GET_32BE(s + 20); if (width > max_width || height > max_height) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "PNG size over(%ld,%ld)", width, height); return broken_image_PNG(f->next, bb); } // ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "PNG(%ld,%ld)", width, height); s[26] = 0x00; /* compress type */ s[27] = 0x00; /* filter type */ s[28] &= 0x01; /* Adam7 */ PUT_32GE(s + 29, calc_crc32(s + 12, (apr_size_t)17)); next_e = MY_BUCKET(f, buf1); PUT_32GE((unsigned char *)next_e->data, sizeof(antixss)-1+(sizeof(buf1)-1-8)); APR_BUCKET_INSERT_AFTER(e, next_e); antixss_e = MY_BUCKET(f, antixss); crc32 = update_crc32(0xffffffffL, (unsigned char *)(buf1 + 4), (sizeof(buf1)-1-4)); crc32 = update_crc32(crc32, (unsigned char *)antixss, sizeof(antixss)-1); crc32 ^= 0xffffffffL; APR_BUCKET_INSERT_AFTER(next_e, antixss_e); crc_e = MY_BUCKET(f, buf2); PUT_32GE((unsigned char *)crc_e->data, crc32); APR_BUCKET_INSERT_AFTER(antixss_e, crc_e); return ap_pass_brigade(f->next, bb); } static int imagefight_filter_JPEG(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *e, *next_e, *head_e, *comment_e, *antixss_e; unsigned char *s_head, *s; apr_status_t rv; apr_size_t dummy; unsigned char comment_header[] = "\xFF\xFE\x00\x00"; PUT_16BE(comment_header + 2, sizeof(antixss) - 1); e = split_rewritable_bucket(f, bb, (apr_bucket *)NULL, (apr_size_t)2); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "e(%08x),", e); if (e == NULL) { return broken_image_JPEG(f->next, bb); } s = (unsigned char *)e->data; // rv = apr_bucket_read(e, &s, &dummy, APR_BLOCK_READ); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "s(%08x),", s); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "s[0](%02x),", s[0]); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "s[1](%02x),", s[1]); if (!(s[0] == 0xff && s[1] == 0xd8)) { return broken_image_JPEG(f->next, bb); } comment_e = MY_BUCKET(f, comment_header); APR_BUCKET_INSERT_AFTER(e, comment_e); antixss_e = MY_BUCKET(f, antixss); APR_BUCKET_INSERT_AFTER(comment_e, antixss_e); return ap_pass_brigade(f->next, bb); } int sanitize_RGB(int z) { if (z == '<') return ++z; if (z == '>') return ++z; return z; } static int imagefight_filter_BMP(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *e, *palt_e, *antixss_e, *body_e; unsigned char *s, *x; apr_status_t rv; unsigned long width, height, bfSize, biSizeImage, bfOffset; int biBitCount, biClrUsed, biClrImportant; int ColorTableSize; e = split_rewritable_bucket(f, bb, (apr_bucket *)NULL, (apr_size_t)54); if (e == NULL) { return broken_image_BMP(f->next, bb); } s = (unsigned char *)e->data; if (!(s[0] == 0x42 && s[1] == 0x4d)) { return broken_image_BMP(f->next, bb); } bfSize = GET_32LE(s + 2); PUT_32LE(s + 2, 0); // TEST width = GET_32LE(s + 18); height = GET_32LE(s + 22); if (width > max_width || height > max_height) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "BMP size over(%ld,%ld)", width, height); return broken_image_BMP(f->next, bb); } s[6] = 0; s[7] = 0; s[8] = 0; s[9] = 0; bfOffset = GET_32LE(s + 10); PUT_32LE(s + 14, 40); PUT_16LE(s + 26, 1); biBitCount = s[28]; // 1,4,8,16,24 if (biBitCount == 1) { ColorTableSize = 2; } else if (biBitCount == 4) { ColorTableSize = 16; } else if (biBitCount == 8) { ColorTableSize = 256; } else if (biBitCount == 16) { ColorTableSize = 0; } else if (biBitCount == 24) { ColorTableSize = 0; } else { ColorTableSize = 0; s[28] = 24; // FIXME } s[29] = 0; s[30] &= 0x03; s[31] = 0; s[32] = 0; s[33] = 0; biSizeImage = GET_32LE(s + 34); /* If 0, IE cannot view compressed BMP. But Firefox is ok. */ if (s[30] == 0) { PUT_32LE(s + 34, 0); } PUT_32LE(s + 38, 0); PUT_32LE(s + 42, 0); biClrUsed = GET_32LE(s + 46); switch (biClrUsed) { case 0: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 236: case 256: break; default: PUT_32LE(s + 46, 0); } biClrImportant = GET_32LE(s + 50); if (!(biClrImportant == 0 || biClrImportant == 9 || biClrImportant == 236)) { PUT_32LE(s + 50, 0); } /* // ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "BMP(%ld,%ld,%ld,%ld)", filesize, compsize, width, height); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "biClrUsed(%d),", biClrUsed); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "biBitCount(%d),", biBitCount); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "biClrImportant(%d),", biClrImportant); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "bfOffset(%d),", bfOffset); */ /* sanitize color table */ if (bfOffset > 54) { int i, n; n = bfOffset - 54; palt_e = split_rewritable_bucket(f, bb, APR_BUCKET_NEXT(e), (apr_size_t)(n)); x = (unsigned char *)palt_e->data; for (i = 0; i < n; i++) { x[i] = ((i % 4) == 3) ? 0 : sanitize_RGB(x[i]); } body_e = APR_BUCKET_NEXT(palt_e); } else { body_e = APR_BUCKET_NEXT(e); } /* insert anti-XSS-RFI attacks code */ antixss_e = MY_BUCKET(f, space200); APR_BUCKET_INSERT_BEFORE(body_e, antixss_e); bfOffset += sizeof(space200) - 1; PUT_32LE(s + 10, bfOffset); /* FIXME: change filesize (don't need) */ bfSize += sizeof(space200) - 1; PUT_32LE(s + 2, bfSize); PUT_32LE(s + 2, 0); // ok return ap_pass_brigade(f->next, bb); } static int imagefight_filter_GIF(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *e, *palt_e, *antixss_e, *body_e, *head_e, *null_e; unsigned char *s, *x; apr_status_t rv; unsigned long width, height; int flag, bGCTF1, bCR3, bSF1, bSGCT3; int global_color_table_size; unsigned char buf1[] = "\x21\xfe\x00"; unsigned char buf2[] = "\x00"; e = split_rewritable_bucket(f, bb, (apr_bucket *)NULL, (apr_size_t)13); if (e == NULL) { return broken_image_GIF(f->next, bb); } s = (unsigned char *)e->data; if (!(s[0] == 'G' && s[1] == 'I' && s[2] == 'F')) { return broken_image_GIF(f->next, bb); } if (!(s[3] == '8' && (s[4] == '9' || s[4] == '7') && s[5] == 'a')) { return broken_image_GIF(f->next, bb); } width = GET_16LE(s + 6); height = GET_16LE(s + 8); if (width > max_width || height > max_height) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "GIF size over(%ld,%ld)", width, height); return broken_image_GIF(f->next, bb); } flag = s[10]; bGCTF1 = flag >> 7; bCR3 = (flag >> 6) & 0x07; bSF1 = (flag >> 3) & 0x01; bSGCT3 = (flag & 0x07); if (bGCTF1 == 1) { // $SizeOfGlobalColorTable = 2 ** (($PackedFields18[$Gif] & 0x07) + 1); global_color_table_size = 1 << (bSGCT3 + 1); } else { global_color_table_size = 0; } s[11]; // Background Color Index s[12] = 0; // Pixel Aspect Ratio ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "global_color_table_size(%d),", global_color_table_size); /* sanitize color table */ if (global_color_table_size > 0) { int i, n; n = global_color_table_size * 3; palt_e = split_rewritable_bucket(f, bb, APR_BUCKET_NEXT(e), (apr_size_t)(n)); x = (unsigned char *)palt_e->data; for (i = 0; i < n; i++) { x[i] = sanitize_RGB(x[i]); } body_e = APR_BUCKET_NEXT(palt_e); } else { body_e = APR_BUCKET_NEXT(e); } /* insert anti-XSS-RFI attacks code */ antixss_e = MY_BUCKET_with_null(f, antixss); buf1[2] = sizeof(antixss) - 1; head_e = MY_BUCKET(f, buf1); APR_BUCKET_INSERT_BEFORE(body_e, antixss_e); APR_BUCKET_INSERT_BEFORE(antixss_e, head_e); return ap_pass_brigade(f->next, bb); } static int imagefight_filter(ap_filter_t* f, apr_bucket_brigade* bb) { char* ctxt = (char*)f->ctx ; if (ctxt == NULL) { imagefight_filter_init(f); ctxt = f->ctx; } else { // return ap_pass_brigade(f->next, bb); } /* pass 304... */ if (f->r->status != 200 || f->r->header_only) { return ap_pass_brigade(f->next, bb); } if (APR_BRIGADE_EMPTY(bb)) { return ap_pass_brigade(f->next, bb); } if (0) { /* FIXME: debug only */ apr_table_setn(f->r->headers_out, "Pragma", "no-cache"); apr_table_setn(f->r->headers_out, "Cache-Control", "no-cache"); } // for mod_proxy_http.c apr_table_unset(f->r->headers_out, "Content-Length"); if (!strncmp(f->r->content_type, "image/gif", 9)) { return imagefight_filter_GIF(f, bb); } else if (!strncmp(f->r->content_type, "image/jpeg", 10)) { return imagefight_filter_JPEG(f, bb); } else if (!strncmp(f->r->content_type, "image/png", 9) || !strncmp(f->r->content_type, "image/x-png", 11)) { return imagefight_filter_PNG(f, bb); } else if (!strncmp(f->r->content_type, "image/bmp", 9) || !strncmp(f->r->content_type, "image/x-bmp", 11)) { return imagefight_filter_BMP(f, bb); } else { /* FIXME: raise error */ return ap_pass_brigade(f->next, bb); } } static void imagefight_register_hooks(apr_pool_t *p) { ap_register_output_filter(imagefight_name, imagefight_filter, imagefight_filter_init, AP_FTYPE_RESOURCE-2) ; } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA imagefight_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ imagefight_register_hooks /* register hooks */ };