├── README.md ├── config.ini └── waffle.php /README.md: -------------------------------------------------------------------------------- 1 | # WAFFLE 2 | 3 | WAFFLE stands for Web Application Firewall For Limited Exploitation and is **a simple but powerful** WAF still under development that provides maximum security against common web threats like XSS Attacks,SQL Injections,Command Injections e.t.c. 4 | 5 | It also provides some extra security features that will be clearly analyzed in the documentation that will be provided in the next few weeks. 6 | 7 | **WAFFLE is currently tested with** 8 | 9 | * Commix 10 | * sqlmap 11 | * acunetix 12 | 13 | **In** 14 | 15 | * Damn Vulnerable Web Application(DVWA) 16 | * Commix-testbed 17 | * Self Made Apps 18 | 19 | **and prevented any possible threats.** 20 | 21 | WAFFLE Differs from other Open Source WAFs, because it doesn't have a rule-set and does not end the script execution. It simply sanitizes any User Input and returns the data securely to your Application. 22 | 23 | #### Advantages 24 | * User Friendly 25 | * Easy to configure 26 | * Easy to install 27 | * Fast in processing 28 | 29 | #### Features / Protections Against 30 | 31 | * SQL Injection 32 | * XSS 33 | * LFI/RFI 34 | * Command Injections 35 | * TOR Exit Nodes 36 | * Non Anonymous Proxies 37 | * Sensitive Files/Dirs with HTTP Authentication 38 | * CPU High Load (Load Average) 39 | * Max Input Limitations 40 | * Allowed Input Methods 41 | * Bad User agents 42 | * DDos 43 | * Brute Force Attacks 44 | * Force HTTPs Usage 45 | 46 | ### KNOWN ISSUES 47 | 48 | * php://input can't be filtered yet. You can however use $HTTP_RAW_POST_DATA in your Application safely 49 | 50 | E-mail: acerockson@hotmail.com 51 | 52 | 53 | ### INSTALLATION 54 | 55 | Simply Upload the waffle.php & config.ini in your preferable directory. 56 | 57 | Open `php.ini` 58 | 59 | Find 60 | 61 | `auto_prepend_file =` 62 | 63 | Replace With 64 | 65 | `auto_prepend_file = /your/directory/waffle.php` 66 | 67 | Restart Apache2 OR PHP-FPM and you are done! 68 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [main] 2 | cache_dir = /tmp/ ; It is recommended to add a RAM-DISK to it for better perfomance :-) 3 | 4 | ;applies also to dirs 5 | [protect_files] 6 | enable_file_protection = true 7 | files = 'admin|administrator|manager|manage' 8 | username = admin 9 | password = waffle_protection 10 | 11 | ;it can also apply for brute force protection 12 | [ddos] 13 | protect_ddos = false 14 | requests_limit = 100 ; if user violates the Frequency X times in a row, he will get banned 15 | frequency = 2 ; Define the allowed frequency limit in seconds / request 16 | use_iptables = false ; It requires sudo to the Running User . Please See Documentation. Linux Only. Highly Recommended 17 | 18 | [resources] 19 | cpu_loadavg_protect = false 20 | cpu_loadavg_limit = 5 ;if load average >= X then we will force the application to exit without initializing other services (such as SQL) which can take more resources 21 | message_exit = 'Server too busy. Please try again later.' 22 | 23 | [proxies] 24 | tor_protection = true 25 | proxies_protection = false 26 | proxies_headers = HTTP_VIA, HTTP_X_FORWARDED_FOR, HTTP_FORWARDED_FOR, HTTP_X_FORWARDED, HTTP_CLIENT_IP, HTTP_FORWARDED_FOR_IP, VIA, X_FORWARDED_FOR, FORWARDED_FOR, X_FORWARDED, FORWARDED, CLIENT_IP, FORWARDED_FOR_IP, HTTP_PROXY_CONNECTION 27 | 28 | [https] 29 | use_only_https = false 30 | redirect = false 31 | 32 | [input] 33 | query_string_max = 255 34 | max_input_elements = 30 35 | max_strlen_var = 0 36 | allowed_methods = GET, POST, PUT, HEAD 37 | 38 | [user_agents] 39 | user_agent_protection = false 40 | block_empty_ua = true 41 | user_agents = 'curl|wget|winhttp|HTTrack|clshttp|loader|email|harvest|extract|grab|miner|libwww-perl|acunetix|sqlmap|python|nikto|scan|commix' 42 | -------------------------------------------------------------------------------- /waffle.php: -------------------------------------------------------------------------------- 1 | = $config['resources']['cpu_loadavg_limit'] ) 17 | { 18 | echo $config['resources']['message_exit']; 19 | exit; 20 | } 21 | } 22 | 23 | if ( $config['ddos']['protect_ddos'] ) 24 | { 25 | $user_file = CACHE_DIR . $user_ip; 26 | 27 | if ( file_exists( $user_file ) ) 28 | { 29 | $flood_row = json_decode( file_get_contents( $user_file ), true ); 30 | 31 | if ( $flood_row['banned'] ) 32 | { 33 | shell_exec( "sudo /sbin/iptables -A INPUT -s $user_ip -j DROP" ); 34 | exit; 35 | } 36 | 37 | if ( time() - $flood_row['last_request'] <= $config['ddos']['frequency'] ) 38 | { 39 | ++$flood_row['requests']; 40 | if ( $flood_row['requests'] >= $config['ddos']['requests_limit'] ) 41 | { 42 | $flood_row['banned'] = true; 43 | } 44 | $flood_row['last_request'] = time(); 45 | file_put_contents( $user_file, json_encode( $flood_row ), LOCK_EX ); 46 | } 47 | else 48 | { 49 | $flood_row['requests'] = 0; 50 | $flood_row['banned'] = false; 51 | $flood_row['last_request'] = time(); 52 | file_put_contents( $user_file, json_encode( $flood_row ), LOCK_EX ); 53 | } 54 | } 55 | else 56 | file_put_contents( $user_file, json_encode( array( 57 | 'banned' => false, 58 | 'requests' => 0, 59 | 'last_request' => time() ) ), LOCK_EX ); 60 | } 61 | 62 | 63 | if ( $config['user_agents']['user_agent_protection'] ) 64 | { 65 | if ( $config['user_agents']['block_empty_ua'] && empty( $_SERVER['HTTP_USER_AGENT'] ) ) 66 | attack_found( "EMPTY USER AGENT" ); 67 | 68 | if ( preg_match( "/^(" . $config['user_agents']['user_agents'] . ").*/i", $_SERVER['HTTP_USER_AGENT'], $matched ) ) 69 | attack_found( "BAD USER AGENT({$_SERVER['HTTP_USER_AGENT']})" ); 70 | } 71 | 72 | 73 | if ( $config['proxies']['tor_protection'] && is_writeable( CACHE_DIR ) ) 74 | { 75 | if ( !file_exists( CACHE_DIR . 'tor_exit_nodes' ) || time() - filemtime( CACHE_DIR . 'tor_exit_nodes' ) >= 1800 ) 76 | { 77 | $source = file_get_contents( "https://check.torproject.org/exit-addresses" ); 78 | if ( preg_match_all( "/ExitAddress (.*?)\s/", $source, $matches ) ) 79 | $ips = $matches[1]; 80 | 81 | file_put_contents( CACHE_DIR . 'tor_exit_nodes', implode( "\n", $ips ) ); 82 | } 83 | else 84 | $ips = array_map( 'trim', file( CACHE_DIR . 'tor_exit_nodes' ) ); 85 | 86 | if ( in_array( $user_ip, $ips ) ) 87 | attack_found( "TOR FOUND ON $user_ip" ); 88 | } 89 | 90 | /* Proxy Access Prtoection */ 91 | foreach ( $proxies_headers as $x ) 92 | { 93 | if ( !empty( $_SERVER[$x] ) ) 94 | attack_found( "PROXIES NOT ALLOWED ($x is SET on \$_SERVER)" ); 95 | } 96 | 97 | if ( strlen( $query_string ) >= $config['input']['query_string_max'] ) 98 | attack_found( "MAX INPUT REACHED! (QUERY STRING: $query_string)" ); 99 | 100 | /* Method Request */ 101 | if ( !in_array( $_SERVER['REQUEST_METHOD'], $allowed_methods ) ) 102 | attack_found( "METHOD ({$_SERVER['REQUEST_METHOD']}) NOT ALLOWED" ); 103 | 104 | /* HTTPS Limitation */ 105 | if ( $config['https']['use_only_https'] && $_SERVER['REQUEST_SCHEME'] != 'https' ) 106 | { 107 | 108 | if ( $config['https']['redirect'] ) 109 | { 110 | header( 'Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301 ); 111 | exit; 112 | } 113 | 114 | attack_found( "NO HTTPS SCHEME FOUND" ); 115 | } 116 | 117 | 118 | /* Protect Files/Folders With Extra Password */ 119 | if ( $config['protect_files']['enable_file_protection'] && !empty( $_SERVER['SCRIPT_NAME'] ) && stristr( $_SERVER['SCRIPT_NAME'], '/' ) ) 120 | { 121 | $auth = false; 122 | if ( isset( $_SERVER['PHP_AUTH_USER'] ) ) 123 | { 124 | $username = @$_SERVER['PHP_AUTH_USER']; 125 | $password = @$_SERVER['PHP_AUTH_PW']; 126 | 127 | if ( $username == $config['protect_files']['username'] && $password == $config['protect_files']['password'] ) 128 | { 129 | $auth = true; 130 | } 131 | } 132 | 133 | if ( !$auth ) 134 | { 135 | $files = explode( '/', $_SERVER['SCRIPT_NAME'] ); 136 | 137 | foreach ( $files as $file ) 138 | { 139 | $file = pathinfo( $file )['filename']; 140 | 141 | if ( preg_match( "/^\b(" . $config['protect_files']['files'] . ")\b/i", $file, $matched ) ) 142 | { 143 | header( 'WWW-Authenticate: Basic realm="WAFFLE Protection"' ); 144 | header( 'HTTP/1.0 401 Unauthorized' ); 145 | exit; 146 | } 147 | } 148 | } 149 | 150 | } 151 | 152 | 153 | if ( empty( $_COOKIE ) ) 154 | $_COOKIE = array(); 155 | 156 | if ( empty( $_SESSION ) ) 157 | $_SESSION = array(); 158 | 159 | $exclude_keys = array( 160 | '__utmz', 161 | '__utma', 162 | '__cfduid', 163 | '_ga' ); 164 | 165 | $WAF_array = array( 166 | '_GET' => &$_GET, 167 | '_POST' => &$_POST, 168 | '_REQUEST' => &$_REQUEST, 169 | '_COOKIE' => &$_COOKIE, 170 | '_SESSION' => &$_SESSION, 171 | '_SERVER' => array( 'HTTP_USER_AGENT' => &$_SERVER['HTTP_USER_AGENT'], 'HTTP_REFERER' => &$_SERVER['HTTP_REFERER'] ), 172 | 'HTTP_RAW_POST_DATA' => array( file_get_contents( 'php://input' ) ) ); 173 | 174 | foreach ( $WAF_array as $key => $array ) 175 | { 176 | if ( count( $array ) > $config['input']['max_input_elements'] ) 177 | attack_found( "MAX INPUT VARS ON $key ARRAY REACHED!" ); 178 | 179 | 180 | foreach ( $array as $k => $v ) 181 | { 182 | if ( in_array( $k, $exclude_keys, true ) ) 183 | { 184 | continue; 185 | } 186 | 187 | if ( $config['input']['max_strlen_var'] != 0 && strlen( $v ) > $config['input']['max_strlen_var'] ) 188 | attack_found( "MAX INPUT ON VAR REACHED!" ); 189 | 190 | 191 | if ( !IsBase64( $v ) ) 192 | ${$key}[SanitizeNClean( $k )] = SanitizeNClean( $v ); 193 | else 194 | ${$key}[SanitizeNClean( $k )] = base64_encode( SanitizeNClean( base64_decode( $v ) ) ); 195 | 196 | } 197 | } 198 | 199 | /* 200 | Get CPU Load Average on Linux/Windows 201 | Warning: On Windows might take some time 202 | */ 203 | function get_server_load() 204 | { 205 | if ( stristr( PHP_OS, 'win' ) ) 206 | { 207 | 208 | $wmi = new COM( "Winmgmts://" ); 209 | $server = $wmi->execquery( "SELECT LoadPercentage FROM Win32_Processor" ); 210 | 211 | $cpu_num = 0; 212 | $load_total = 0; 213 | 214 | foreach ( $server as $cpu ) 215 | { 216 | $cpu_num++; 217 | $load_total += $cpu->loadpercentage; 218 | } 219 | 220 | $load = round( $load_total / $cpu_num ); 221 | 222 | } 223 | else 224 | { 225 | 226 | $sys_load = sys_getloadavg(); 227 | $load = $sys_load[0]; 228 | 229 | } 230 | 231 | return ( int )$load; 232 | 233 | } 234 | 235 | function attack_found( $match ) 236 | { 237 | file_put_contents( CACHE_DIR . 'attacks.txt', "[WARNING] Possible Threat Found ( => '" . str_replace( "\n", "\\n", $match ) . "' <= ) @ " . date( "F j, Y, g:i a" ) . "\n", FILE_APPEND ); 238 | exit( 'Hacking Attempt Detected & Eliminated' ); 239 | } 240 | 241 | function SanitizeNClean( $string ) 242 | { 243 | return htmlentities( str_replace( array( 244 | '(', 245 | ')', 246 | '=', 247 | ',', 248 | '|', 249 | '$', 250 | '`', 251 | '/', 252 | '\\' ), array( 253 | '(', 254 | ')', 255 | '=', 256 | ',', 257 | '|', 258 | '$', 259 | '`', 260 | '/', 261 | '\' ), urldecode( $string ) ), ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE, ini_get( "default_charset" ), false ); 262 | } 263 | 264 | function IsBase64( $string ) 265 | { 266 | $d = base64_decode( $string, true ); 267 | return ( !empty( $d ) ) ? isAscii( $d ) : false; 268 | 269 | } 270 | 271 | function isAscii( $str ) 272 | { 273 | return preg_match( '/^([\x00-\x7F])*$/', $str ); 274 | } 275 | --------------------------------------------------------------------------------