The following code processes the request in the source code src/main/http_request.c:
Static void process_request_internal (request_rec * r)
{
Int access_status;
/* Ignore embedded % 2f's in path for proxy requests */
If (R-> proxyreq = not_proxy & R-> parsed_uri.path ){
/* If it is not a proxy */
Access_status = ap_unescape_url (R-> parsed_uri.path );
/* Decoding %, RFC requirements */
If (access_status ){
Ap_die (access_status, R );
Return;
}
}
Ap_getparents (R-> URI);/* OK --- shrinking transformations ...*/
/* Process/../, error, not considering/../*/under Windows /../*/
If (access_status = location_walk (R ))){
Ap_die (access_status, R );
Return;
}
If (access_status = ap_translate_name (R ))){
Decl_die (access_status, "translate", R );
Return;
}
If (R-> proxyreq = not_proxy ){
/*
* We don't want trace to run through the normal handler set, we
* Handle it specially.
*/
If (R-> method_number = m_trace ){
If (access_status = ap_send_http_trace (R )))
Ap_die (access_status, R );
Else
Ap_finalize_request_protocol (R );
Return;
}
}
If (R-> proto_num> http_version (1.0) & ap_table_get (R-> subprocess_env, "downgrade ")){
R-> proto_num = http_version (1, 0 );
}
/*
* NB: directory_walk () clears the per_dir_config, so we don't inherit
* From location_walk () abve
*/
If (access_status = directory_walk (R ))){
Ap_die (access_status, R );
Return;
}
If (access_status = file_walk (R ))){
Ap_die (access_status, R );
Return;
}
If (access_status = location_walk (R ))){
Ap_die (access_status, R );
Return;
}
If (access_status = ap_header_parse (R ))){
Ap_die (access_status, R );
Return;
}
Switch (ap_satisfies (R )){
Case satisfy_all:
Case satisfy_nospec:
If (access_status = ap_check_access (R ))! = 0 ){
Decl_die (access_status, "Check access", R );
Return;
}
If (ap_some_auth_required (R )){
If (access_status = ap_check_user_id (R ))! = 0 )! Ap_auth_type (R )){
Decl_die (access_status, ap_auth_type (r)
? "Check user. No user file? "
: "Perform authentication. authtype not set! ", R );
Return;
}
If (access_status = ap_check_auth (R ))! = 0 )! Ap_auth_type (R )){
Decl_die (access_status, ap_auth_type (r)
? "Check access. No groups file? "
: "Perform authentication. authtype not set! ", R );
Return;
}
}
Break;
Case satisfy_any:
If (access_status = ap_check_access (R ))! = 0 )! Ap_auth_type (R )){
If (! Ap_some_auth_required (R )){
Decl_die (access_status? Access_status:
Http_internal_server_error,
Ap_auth_type (r )? "Check access"
: "Perform authentication. authtype not set! ", R );
Return;
}
If (access_status = ap_check_user_id (R ))! = 0 )! Ap_auth_type (R )){
Decl_die (access_status, ap_auth_type (r)
? "Check user. No user file? "
: "Perform authentication. authtype not set! ", R );
Return;
}
If (access_status = ap_check_auth (R ))! = 0 )! Ap_auth_type (R )){
Decl_die (access_status, ap_auth_type (r)
? "Check access. No groups file? "
: "Perform authentication. authtype not set! ", R );
Return;
}
}
Break;
}
If (! (R-> proxyreq! = Not_proxy
& R-> parsed_uri.scheme! = NULL
& Strcmp (R-> parsed_uri.scheme, "HTTP") = 0 )){
If (access_status = ap_find_types (R ))! = 0 ){
Decl_die (access_status, "Find types", R );
Return;
}
}
If (access_status = ap_run_fixups (R ))! = 0 ){
Ap_die (access_status, R );
Return;
}
If (access_status = ap_invoke_handler (R ))! = 0 ){
Ap_die (access_status, R );
Return;
}
/* Take care of little things that need to happen when we're done */
Ap_finalize_request_protocol (R );
}
The following code processes the source code src/main/util. c:
Api_export (void) ap_getparents (char * name)
{
Int L, W;
/* Four paseses, as per RFC 1808 */
/* A) Remove./path segments */
For (L = 0, W = 0; name [l]! = '/0 ';){
If (name [l] = '. '& name [L + 1] ='/'& (L = 0 name [L-1] = '/'))
L + = 2;
Else
Name [W ++] = Name [L ++];
}
/* B) Remove trailing. Path, segment */
If (W = 1 & name [0] = '.')
W --;
Else if (W> 1 & name [w-1] = '.' & name [w-2] = '/')
W --;
Name [w] = '/0 ';
/* C) Remove all XX/../segments. (including leading.../AND /../)*/
L = 0;
While (name [l]! = '/0 '){
If (name [l] = '.' & name [L + 1] = '.' & name [L + 2] = '/'&&
(L = 0 name [L-1] = '/')){
Register int M = L + 3, N;
L = L-2;
If (L> = 0 ){
While (L> = 0 & name [l]! = '/')
L --;
L ++;
}
Else
L = 0;
N = L;
While (name [N] = Name [m])
(++ N, ++ m );
}
Else
+ + L;
}
/* D) Remove trailing XX/... segment .*/
If (L = 2 & name [0] = '.' & name [1] = '.')
Name [0] = '/0 ';
Else if (L> 2 & name [L-1] = '. '& name [L-2] = '. '& name [L-3] = '/'){
L = L-4;
If (L> = 0 ){
While (L> = 0 & name [l]! = '/')
L --;
L ++;
}
Else
L = 0;
Name [l] = '/0 ';
}
}
As you can see, the "/../" under Windows is obviously not taken into account. It seems that the person who compiled this function is not familiar with the features of windows and is a programmer who has been programming under * UNIX for a long time. In fact, there have been many problems with "/../" under windows. Why can't I use "/../" for telnet? It seems that all "/" is replaced with "/" before decoding. Obviously, this conversion should be performed after decoding. It is estimated that "/.../" is not detected because the conversion has been performed first, which is the same as the % C1 % 1C vulnerability in IIS. In this way, you must pay attention to the previous checks if there is any encoding or decoding. If not, you must re-encode the detected items.
Here is the processing module for converting "/" In Uri into "/" in the source code src/main/http_protocol.c:
/* Parse_uri: break apart the URI
* Side effects:
*-Sets R-> ARGs to rest after '? '(Or null if no '? ')
*-Sets R-> URI to request URI (without R-> ARGs part)
*-Sets R-> hostname (if not set already) from request (scheme: // host: Port)
*/
Core_export (void) ap_parse_uri (request_rec * r, const char * URI)
{
Int status = http_ OK;
R-> unparsed_uri = ap_pstrdup (R-> pool, Uri );
If (R-> method_number = m_connect ){
Status = ap_parse_hostinfo_components (R-> pool, Uri, & R-> parsed_uri );
} Else {
/* Simple syntax errors in URLs are trapped by parse_uri_components ().*/
Status = ap_parse_uri_components (R-> pool, Uri, & R-> parsed_uri );
}
If (ap_is_http_success (Status )){
/* If it has a scheme we may need to do absoluteuri vhost stuff */
If (R-> parsed_uri.scheme
&&! Strcasecmp (R-> parsed_uri.scheme, ap_http_method (R ))){
R-> hostname = r-> parsed_uri.hostname;
} Else if (R-> method_number = m_connect ){
R-> hostname = r-> parsed_uri.hostname;
}
R-> ARGs = r-> parsed_uri.query;
R-> uri = r-> parsed_uri.path? R-> parsed_uri.path
: Ap_pstrdup (R-> pool ,"/");
# If defined (os2) defined (win32)
/* Handle path translations for OS/2 and plug security hole.
* This will prevent "http://www.enet.com.cn/../..//" from
* Returning a directory for the root drive.
*/
{
Char * X;
For (x = r-> URI; (x = strchr (x ,'//'))! = NULL ;)
* X = '/';
/*/Convert /*/
}
# Endif/* os2 Win32 */
}
Else {
R-> ARGs = NULL;
R-> hostname = NULL;
R-> Status = status;/* set error status */
R-> uri = ap_pstrdup (R-> pool, Uri );
}
}