PHP rocks! wünscht allen Mitgliedern einen guten Rutsch ins neue Jahr 2017 !!!
Hinweis: Das Forum zieht um! Um keine Datenverluste zu haben, schalten wir zwecks Übernahme der Daten das Forum am Sonntag, den 24.04.2016 um ca. 21:00 Uhr offline und passen anschliessend die DNS-Einträge an.
www.php-rocks.de wird euch dann nach den Aktualisierungen der DNS-Server wieder wie gewohnt uneingeschränkt zur Verfügung stehen.
Danke für euer Verständnis!

Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
var_export unserialize error
#1
Ich möchte API Ergebnisse mit var_export cachen.
Es gibt leider Fehler beim include, z.B. bei folgender Datei: https://github.com/frdl/webfan/blob/master/composer.json bzw. dem entsprechenden Suchergebnis ( nach var_exports(json_decode()) )
Der (JSON) token
Code:
\\"
\ wirdfälschlicherweise als escape von \ interpretiert (?) und verursacht den Fehler:
Fatal Error syntax error, unexpected T_STRING, expecting ')'

cache.php (nur noch ein \ wieder ein falsches Escape):
PHP-Code:
...
               
'psr-4' => 
              (object)(array(
                 
'frdl\' => '.ApplicationComposer/lib/frdl/',
                 '
webfan\' => '.ApplicationComposer/lib/webfan/',
                 '
webdof\' => '.ApplicationComposer/lib/webdof/',
                 '
_empty_\' => '.ApplicationComposer/lib/',
              )),
... 

Folgendes habe ich bisher versucht:
PHP-Code:
$code str_replace(array("stdClass::__set_state"'o::__set_state''\o::__set_state''O::__set_state''\O::__set_state'), array("(object)""(object)""(object)""(object)""(object)"), var_export($valuetrue));
$code str_replace("\' => '""\\' => '"$code); 
Klappt aber irgendwie nicht (mehr).
Und gefällt mir auch noch überhaupt nicht, da ich die gleiche Klasse auch für andere Daten verwende bei denen theorertisch das str_replace Fehler verursachen könnte.

EDIT:
Das json_decode funktioniert, der Fehler tritt nur auf wenn aus dem cache.
Ich habe das str_replace vom php code entfernt und es versucht mit
PHP-Code:
$body str_replace('\\":"''\\\":"'$body); 
vor dem json_decode, leider ohne Erfolg.
Antworten
#2
Hi,

Ich denke, Du benötigst einen Slash mehr:
PHP-Code:
$body str_replace('\\":"''\\\\":"'$body); 
Antworten
#3
Mh, damit kommen keine Ergebnisse bzw. sie werden getilgt:
packageinfo : null
Antworten
#4
Ich steige nicht durch die Fragestellung bzw. kann das Verhalten nicht reproduzieren. Welche PHP-Version ist das?

Es gibt prinzipiell diesen Bug, dass stdClass kein __set_state implementiert:

- https://bugs.php.net/bug.php?id=48016

Wäre ein assoziatives Array (zweiter Parameter von json_decode) vielleicht erst mal die bessere Option?
Antworten
#5
Hallo mermshaus,
Zitat:Es gibt prinzipiell diesen Bug, dass stdClass kein __set_state implementiert:
Dies versuche ich mit der Zeile zu beheben:
Zitat: $code = str_replace(array("stdClass::__set_state", 'o::__set_state', '\o::__set_state', 'O::__set_state', '\O::__set_state'), array("(object)", "(object)", "(object)", "(object)", "(object)"), var_export($value, true));
Ich glaube nicht das dies das Problem ist (?), var_exported wird:
PHP-Code:
'psr-4' => 
              (object)(array(
                 
'frdl\' => '.ApplicationComposer/lib/frdl/',
                 '
webfan\' => '.ApplicationComposer/lib/webfan/',
                 '
webdof\' => '.ApplicationComposer/lib/webdof/',
                 '
_empty_\' => '.ApplicationComposer/lib/',
              )), 
richtig wäre m.e.
PHP-Code:
'psr-4' => 
              (object)(array(
                 
'frdl\\' => '.ApplicationComposer/lib/frdl/',
                 
'webfan\\' => '.ApplicationComposer/lib/webfan/',
                 
'webdof\\' => '.ApplicationComposer/lib/webdof/',
                 
'_empty_\\' => '.ApplicationComposer/lib/',
              )), 

Zitat:Wäre ein assoziatives Array (zweiter Parameter von json_decode) vielleicht erst mal die bessere Option?
Daran hab ich auch schon gedacht, ich hätte zwar psr-4 lieber "original" als Object, werde das aber später oder morgen mal probieren (im Moment bin ich zu müde).

Zitat: Welche PHP-Version ist das?
5.3.3

Zitat:Ich steige nicht durch die Fragestellung bzw. kann das Verhalten nicht reproduzieren.

Ok, hier nochmal mein kompletter relevanter code, in der ersten box die methoden der API (requerst) in er zweiten box die cache_class
PHP-Code:
protected function url($base$uri){
      return 
$base.$uri;
   }

   protected function 
_search_packagist($query$willCacheThisFor '600 seconds', &$error null, array $search = array())
    {
        
$http_opts = array('method'=>'GET','X-frdl-proxy-will-cache-for'=>$willCacheThisFor);
        
        
$result = array();
        
        

        
        
$search['q'] = $query;
        
$url =  $this->url('https://packagist.org''/search.json?' http_build_query($search));

        
$r $this->request($url$http_opts$body$error);
        
        if(
true !== $r)return false;
        try{
            
$r json_decode($body);
            
$result $r->results;
           
        
$last $url;   
        if(isset(
$r->next))
        {
        do {
            
$_opts array_merge($http_opts, array('referer'=>$last));
            
usleep(5000);
            
$_r $this->request($r->next$_opts$body$error);
            
$last $r->next;
             if(
true !== $_r)return $result;
             
$r json_decode($body);
            
$result array_merge($result$r->results);
        } while (isset(
$r->next));            
        }

    
            
        }catch(\
Exception $e){
            if(
MX_IS_ADMIN)trigger_error($e->getMessage(), E_USER_ERROR);
            return 
false;
        }
        
        return 
$result;
    }
   
   
   
    protected function 
_search_phpclasses($query$willCacheThisFor '600 seconds', &$error null){
        
           
$http_opts = array('method'=>'GET','X-frdl-proxy-will-cache-for'=>$willCacheThisFor);
        
        
$result = array();
        
$this->_get_phpclasses_packages_json($body$error$willCacheThisFor);

        if(
'' !== $error)return false;
  
        try{
            
$r json_decode($body);
            foreach(
$r->packages as $package => $p){  
              
$_c explode('/'$package);
              
$found false;
              if(
false===strpos($query'*') && false===strpos($query'/'))if(preg_match("/".preg_quote($query)."/"$_c[1])) $found true;
              if(
false!==strpos($query'/')){
                  
$qs explode('/'$query);
                  if(
'*'!==$qs[0])if($qs[0]===$_c[1] || preg_match("/".preg_quote($qs[0])."/"$_c[1])) $found true;
                  if(
$qs[1]===$_c[1] || preg_match("/".preg_quote($qs[1])."/"$_c[1])) $found true;
              }
              if(
true!==$found)continue;
              foreach(
$p as $_v => $v){
                 
$result array_merge($result, array($v));
              
              }

            }         
    
        }catch(\
Exception $e){
            if(
MX_IS_ADMIN)trigger_error($e->getMessage(), E_USER_ERROR);
            return 
false;
        }
        
        
        return 
$result;           
   }
     
   
   

    protected function 
request($url$options=array(), &$body null, &$error null)
    {
        
$def_opts = array(
          
'method'=>'GET',
          
'user_agent'=>'Webfan.de API',
          
'referer'=>'http://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'],
          
'debug'=>0,
          
'html_debug'=>0,
          
'follow_redirect'=>1,
          
'X-frdl-proxy-will-cache-for'=>'600 seconds'
        
);
        
        
$opts array_merge($def_opts$options);
        
 
$cache_prefix "proxy GET 29";
$cachekey $url;   
$nlimit 30 60;
$cache = new \class_cache();
$body =  $cache -> load($cachekey$cache_prefix); 

if(  
$opts['method'] !=='GET' || true !== $cache->use_cache()){
      
        
$classname get_class($this->http_client);
        
$http = new $classname;
        
$http->debug=$opts['debug'];
        
$http->html_debug=$opts['html_debug'];
        
$http->follow_redirect=$opts['follow_redirect'];
       
//  $http->user_agent = $_SERVER['HTTP_USE_AGENT'];   
        
$http->user_agent =$opts['user_agent'];
      
//   $arguments['Headers']['Referer']= $_SERVER['HTTP_REFERER'];    
        
$arguments['Headers']['Referer']= $opts['referer'];
        
        
$error=$http->GetRequestArguments($url,$arguments);

        if(
$error)return false;

        
$error=$http->Open($arguments);
        if(
$error)return false;

       
$arguments['Headers']['X-Forwarded-For']=  $_SERVER['REMOTE_ADDR'];  
        
$arguments['Headers']['X-frdl-proxy']= 'web+fan://interface.api.webfan.de/v1/public/software#proxy:'.$url;  
         
$arguments['Headers']['X-frdl-proxy-will-cache-for']= $opts['X-frdl-proxy-will-cache-for'];
          
$arguments['Headers']['X-frdl-github-app-page']=  'http://frdl.github.io/webfan/';  
        
$arguments['RequestMethod']=$opts['method'];
        
$error=$http->SendRequest($arguments);      
        if(
$error)return false;    
         
$error=$http->ReadReplyHeaders($headers);
        if(
$error)return false;
        
$error $http->ReadWholeReplyBody($body);   
        if(
$error)return false;
        
       
// $body = str_replace('\\":"', '\\\\":"', $body);
        
 
$cache->save($cachekey$body$nlimit$cache_prefix);        
}   


        
        return 
true;
    }
    
    
    
public function 
func_php_search(){
         
  
$this->api->data false;
  
  
$query '';
  
 if(
0<count($this->api->args)){
      
$args $this->api->args;
     
// array_shift($args);
       
$query join('/'$args);
      
 }else{
    if(
''===$query && isset($this->api->request['q'])){
        
$query $this->api->request['q'];
    }elseif(
''===$query && isset($this->api->request['query'])){
        
$query $this->api->request['query'];
    }elseif(
''===$query && isset($this->api->request['search'])){
         
$query $this->api->request['search'];
    }elseif(
''===$query && isset($this->api->request['package'])){
        
$query $this->api->request['package'];
   }
     
 }
 
        if(
false===strpos($query'/')){
            
$query='*/'.$query;
        }


$cache_prefix "proxy-php_search-9";
$cachekey $query;   
$nlimit 60;
$cache = new \class_cache();

try{
 
$result $cache -> load($cachekey$cache_prefix); 
}catch(\
Exception $e){
    
}

if( !
is_array($result) ||  true !== $cache->use_cache()){
   
$result = array();
 
 
$repository  'packagist';
$website 'http://packagist.org';   
$SUB_cache_prefix "web+fan://api.webfan.de.proxy#php_search@1.29/".$repository;
$SUB_cachekey $query.'?website='.$website;   
$SUB_nlimit 30 60;
$SUB_cache = new \class_cache();   
$r =$SUB_cache -> load($SUB_cachekey$SUB_cache_prefix);   
if(   
true !== $SUB_cache->use_cache()){    
   
$r = new \stdclass;
   
$r->repository =$repository;
   
$r->website $website;
   
$r->results $this->_search_packagist($query$nlimit.' seconds'$error);
   if(
$error$r->error $error;
   
$SUB_cache->save($SUB_cachekey$r$SUB_nlimit$SUB_cache_prefix);    
}   
$result[]=$r;
   


 
$repository  'phpclasses';
$website 'http://phpclasses.org';   
$SUB_cache_prefix "web+fan://api.webfan.de.proxy#php_search@1.29/".$repository;
$SUB_cachekey $query.'?website='.$website;   
$SUB_nlimit 30 60;
$SUB_cache = new \class_cache();   
$r =$SUB_cache -> load($SUB_cachekey$SUB_cache_prefix);   
if(  
true !== $SUB_cache->use_cache()){    
   
$r = new \stdclass;
   
$r->repository =$repository;
   
$r->website $website;
   
$r->results $this->_search_phpclasses($query$nlimit.' seconds'$error);
   if(
$error$r->error $error;
   
$SUB_cache->save($SUB_cachekey$r$SUB_nlimit$SUB_cache_prefix);    
}   
$result[]=$r;
   
 
   
   
$cache->save($cachekey$result$nlimit$cache_prefix);    
}




        
 if(
false===$result){
     if(
''===$query) return $this->api->_response(self::E_404404);
     return 
$this->api->_response(self::E_404200);
 }else{
       
$this->api->data $result;
 }        
        

  return 
$this->api->_response(self::E_OK200);        
}



        
   public function 
_package_packagist($vendor$packagename$willCacheThisFor '600 seconds', &$error null){
        
$result false;
        
$url =  $this->url('https://packagist.org''/packages/'.$vendor.'/'.$packagename.'.json');
        
$http_opts = array('method'=>'GET','X-frdl-proxy-will-cache-for'=>$willCacheThisFor);
        
$this->request($url$http_opts$body$error);
        
        if(
$error)return false;
        try{
            
$r json_decode($body);
            
$result $r;
            if(isset(
$result->package->versions))$result->package->versions=(array)$result->package->versions;
            
        }catch(\
Exception $e){
            if(
MX_IS_ADMIN)trigger_error($e->getMessage(), E_USER_ERROR);
            return 
false;
        }
        
        return 
$result;     
   }             

    
   protected function 
_package_phpclasses($vendor$packagename$willCacheThisFor '600 seconds', &$error null){
        
$result false;

        
$this->_get_phpclasses_packages_json($body$error$willCacheThisFor);
        if(
$error)return false;
  
        try{
            
$r json_decode($body);
            foreach(
$r->packages as $package => $p){  
              
$_c explode('/'$package);
              if(
2!== count($_c) || $_c[0] !== $vendor || $_c[1] !== $packagename)continue;
              foreach(
$p as $_v => $v){
                   
$package = new \stdclass;
                   
$package->package = new \stdclass;
                   
$package->package->name $vendor.'/'.$packagename;
                   
$package->package->versions = array($_v => $v);
                 
$result $package;
              }
              break; 
            }         
    
        }catch(\
Exception $e){
            if(
MX_IS_ADMIN)trigger_error($e->getMessage(), E_USER_ERROR);
            return 
false;
        }
        
        return 
$result;            
   }  
   
   
protected function 
_get_phpclasses_packages_json(&$body, &$error$willCacheThisFor '600 seconds'){
 
$SUB_cache_prefix "web+fan://api.webfan.de.proxy#get_phpclasses_packages_json";
$SUB_cachekey 'v9';   
$SUB_nlimit 30 60;
$SUB_cache = new \class_cache();   
$r =$SUB_cache -> load($SUB_cachekey$SUB_cache_prefix);   
if(
true !== $SUB_cache->use_cache()){    
   
$url =  $this->url('http://www.phpclasses.org''/packages.json');
   
$http_opts = array('method'=>'GET','X-frdl-proxy-will-cache-for'=>$SUB_nlimit' seconds or '.$willCacheThisFor);
   
$_r $this->request($url$http_opts$_body$_error);
   
$r = array(
      
'body' => $_body,
      
'error'=>$_error
   
);
   
$SUB_cache->save($SUB_cachekey$r$SUB_nlimit$SUB_cache_prefix);    
}   

$body=$r['body'];
$error=$r['error'];       
}
 
 
 
 
 
 
    
public function 
func_php_package(){
         
  
$this->api->data false;
  
  
$query '';
  
 if(
0<count($this->api->args)){
      
$args $this->api->args;
     
// array_shift($args);
       
$query join('/'$args);
      
 }else{
   if(
''===$query && isset($this->api->request['package'])){
        
$query $this->api->request['package'];
   }
     
 }
 
$req explode('/'$query2);
if(
2!==count($req)){
     return 
$this->api->_response('uri must contain vendor/package'400);
}

$vendor=$req[0];
$packagename=$req[1];


$cache_prefix "api.webfan.de.proxy-php_package@29";
$cachekey '--vendor="'.$vendor.'" --packagename="'.$packagename.'" --softgroup="php" ';   
$nlimit 60;
$cache = new \class_cache();
try{
 
$result $cache -> load($cachekey$cache_prefix);     
}catch(\
Exception $e){
    
}

if(!
is_array($result) || true !== $cache->use_cache()){
   
$result = array();
 
 
$repository  'packagist';
$website 'http://packagist.org';   
$SUB_cache_prefix $cache_prefix."/".$repository;
$SUB_cachekey $cachekey.' --repository="'.$website.'" 19';   
$SUB_nlimit 30 60;
$SUB_cache = new \class_cache();   
$r =$SUB_cache -> load($SUB_cachekey$SUB_cache_prefix);   
if(  
true !== $SUB_cache->use_cache()){    
   
$r = new \stdclass;
   
$r->repository =$repository;
   
$r->website $website;
   
$r->packageinfo $this->_package_packagist($vendor$packagename$SUB_nlimit.' seconds'$error);
   if(
$error$r->error $error;
   
$SUB_cache->save($SUB_cachekey$r$SUB_nlimit$SUB_cache_prefix);    
}   
$result[]=$r;
   


 
 
 
 
  
$repository  'phpclasses';
$website 'http://phpclasses.org';   
$SUB_cache_prefix $cache_prefix."/".$repository;
$SUB_cachekey $cachekey.' --repository="'.$website.'" 19';   
$SUB_nlimit 30 60;
$SUB_cache = new \class_cache();   
$r =$SUB_cache -> load($SUB_cachekey$SUB_cache_prefix);   
if(
true !== $SUB_cache->use_cache()){    
   
$r = new \stdclass;
   
$r->repository =$repository;
   
$r->website $website;
   
$r->packageinfo $this->_package_phpclasses($vendor$packagename$SUB_nlimit.' seconds'$error);
   if(
$error$r->error $error;
   
$SUB_cache->save($SUB_cachekey$r$SUB_nlimit$SUB_cache_prefix);    
}   
$result[]=$r;
      
$cache->save($cachekey$result$nlimit$cache_prefix);    
}




        
 if(
false===$result){
     if(
''===implode(''$req)) return $this->api->_response(self::E_404404);
     return 
$this->api->_response(self::E_404200);
 }else{
       
$this->api->data $result;
 }        
        

  return 
$this->api->_response(self::E_OK200);        




PHP-Code:
<?PHP
/**
 * @autor        Piotr Miloszewicz
 * @www         FiveGroup
 * @name        Cache variables
 */

//define cache path
if(!defined('CACHE_PATH'))define'CACHE_PATH'dirname(__FILE__).'/../../../cache-public-hour/cache-hour-not-public/');

class 
class_cache
{

    private 
$FILENAME_PREFIX 'CACHE.H.';

    public 
$check_cache false;

    
/**
     * save data to cache
     *
     * @param: mixed $name
     * @param: mixed $value
     * @param: (int) $timeout
     */

    
public function name($prefix_extra null$name){
        if(
$prefix_extra === null)$prefix_extra substr($name016);
        
$prefix_extra preg_replace("/[^A-Za-z0-9]/","-"$prefix_extra);
          
$name $this->FILENAME_PREFIX.$prefix_extra.'.'.strlen($prefix_extra).'.'.strlen($name).'.'.sha1($name);    
          return 
$name;    
    }


    public function 
save$name$value$timeout 0$prefix_extra null)
    {

          
$name $this->name($prefix_extra$name);

        
//start cache file
        
$cache_file "<?PHP\n\n";

        
//set description to cache file
        
$cache_file .= "/*\n    CACHE FILE NAME: "$name ."\n    GENERATE TIME: "date('c') ."\n*/\n\n";

        
//set data to cache file
        
$timeout $timeout time();
        
$cache_file .= "//cache timeout\n" '$timeout = '$timeout ";\n\n";

        
//set variable to cache
        
$code str_replace(array("stdClass::__set_state"'o::__set_state''\o::__set_state''O::__set_state''\O::__set_state'), array("(object)""(object)""(object)""(object)""(object)"), var_export($valuetrue));
    
      
//  $code = str_replace("\' => '", "\\' => '", $code);
    
        
$cache_file .= "//cache content\n" '$cache_variable = '$code .';';

        
//end cache
        
$cache_file .= "\n\n?>";

        
//return cache to save
        
$r file_put_contentsCACHE_PATH .'/'$name .'.php'$cache_file);
        
chmod(CACHE_PATH .'/'$name .'.php'0775);
        return 
$r;
    }

    
/**
     * load cache
     *
     * @return: mixed
     */

    
public function load$name$prefix_extra null, &$time null, &$tempfilename null)
    {

         
$name $this->name($prefix_extra$name);
          
          
        
$tempfilename CACHE_PATH .'/'$name .'.php';
        
//chech was cache exists
        
if( file_exists($tempfilename)) {

            
//load cache
            
include $tempfilename;

            
$time $timeout
            
//chech was cache no expired
            
if($timeout time()) {

                
$this->check_cache true;
            }else{
                
$this->check_cache false;
            }

            
// return cache
            
return $cache_variable;

        } else {

            
// return error
            //return "<b>Error</b>\n\n Cache file does not exists.";
            
$this->check_cache false;
        }
    }

    
/**
     * check value of check_cache field
     *
     */

    
public function use_cache() {
        
//return $this->check_cache;
        
if(true === $this->check_cache)return true;
        return 
false;
    }

    
/**
     * delete cache
     *
     * @param: mixed $name;
     */

    
public function flush$name$prefix_extra null) {

        
$name =  $this->name($prefix_extra$name);

        if( 
file_existsCACHE_PATH .'/'$name .'.php')) {
            
chmod(CACHE_PATH .'/'$name .'.php'0775);
            
unlinkCACHE_PATH .'/'$name .'.php');
        }
    }

Antworten
#6
Zitat:5.3.3

- https://3v4l.org/ENgTk

Zitat:$code = str_replace(array("stdClass::__set_state"…

Wobei du dir damit eben eine potenzielle (wenn auch sicherlich unwahrscheinliche) Fehlerquelle einhandelst, indem du die genauen Syntaxregeln von PHP nicht beachtest. So was sind primäre Ziele, über die dann am Ende Software angegriffen und gehackt wird. Du erzeugst ja ausführbaren PHP-Code.

Ich wäre extrem vorsichtig und zurückhaltend mit so was.

Zitat:Ok, hier nochmal mein kompletter relevanter code

Ist nett, aber ~570 Zeilen, die in sich nicht mal lauffähig sind, sind etwas too much als erklärendes Beispiel. Aber es liegt ja an deiner sehr alten PHP-Version.
Antworten
#7
Zitat:Du erzeugst ja ausführbaren PHP-Code.
Ja, ich dachte das wäre der Zweck von var_export?

Zitat:Ich wäre extrem vorsichtig und zurückhaltend mit so was.
Ok, ich werde über Deine Anregung nachdenken

Die Idee dahinter war, das ausführbarer code schneller wieder zu laden ist, als wenn er erst de-serialisiert werden müßte.

Zitat: Aber es liegt ja an deiner sehr alten PHP-Version.
Ok, schade. Dann muß ich das "Problem" erstmal hinten anstellen.

Vielen Dank für Eure Hilfe!

https://3v4l.org/ - kannte ich noch nicht, nice!
Antworten
#8
Zitat:Ist nett, aber ~570 Zeilen, die in sich nicht mal lauffähig sind, sind etwas too much als erklärendes Beispiel.
Anmerkung am Rande, falls es jamand braucht/interessiert:
Der zugrunde liegende Code ist auch hier verfügbar: https://github.com/frdl/package-fetcher/

Die Frage ansich ist beantwortet, danke nochmal!
Antworten
#9
Von 5.3.3 kommst du wirklich nicht irgendwie weg? Du musst ja theoretisch nur bis 5.3.7 upgraden. (Mehr wäre natürlich besser, aber da muss man dann schon eher mal schauen, was Kompatibilität angeht.)

Zitat:Die Idee dahinter war, das ausführbarer code schneller wieder zu laden ist, als wenn er erst de-serialisiert werden müßte.

Ja, das kann grundsätzlich sicher ein Argument sein. (Müsste man im Zweifel irgendwie messen, ob und wie viel es ausmacht.)

Mir ging es konkret um deine Ersetzungen von PHP-Syntax als simplen Strings.

Also das hier:

PHP-Code:
$code str_replace(array("stdClass::__set_state"'o::__set_state''\o::__set_state''O::__set_state''\O::__set_state'), array("(object)""(object)""(object)""(object)""(object)"), var_export($valuetrue));
$code str_replace("\' => '""\\' => '"$code); 

Bei der ersten Zeile habe ich jetzt spontan keine Idee, wie man diese Ersetzungen mit Schadabsicht ausnutzen könnte, aber wenn du in einem String in den Eingabedaten "stdClass::__set_state" stehen hättest, würde das auch dort fälschlicherweise durch "(object)" ersetzt. Einen Forenpost wie diesen, der diese Strings enthält, könntest du also zum Beispiel nicht korrekt cachen.

Die zweite Zeile kann man im Grunde ausklammern, weil das Verhalten, das dort gefixt werden soll, ja ein PHP-Bug ist. Ansonsten taugt das prinzipiell aber schon eher, um aus einem Kontext auszubrechen.

(Für das Beispiel lege ich mir jetzt einige Dinge mehr oder weniger passend zurecht, aber ich versuche dennoch, es mal durchzuexerzieren.)

Eigentlich gemeint ist diese Zeile:

PHP-Code:
$code str_replace("\\' => '""\\\\' => '"$code); 

Das behebt unter 5.3.3 das zu Beginn des Threads besprochene Problem.

PHP-Code:
<?php

$data 
= <<<'EOT'
{
    "frdl\\" : ".ApplicationComposer/lib/frdl/"
}
EOT;

$code var_export(json_decode($data), true);

echo 
str_replace("\\' => '""\\\\' => '"$code); 

Ausgabe:

Code:
stdClass::__set_state(array(
  'frdl\\' => '.ApplicationComposer/lib/frdl/',
))

Sagen wir jetzt mal, dass das Key/Value-Paar in der JSON-Eingabe beliebig vom Nutzer bestimmt werden kann. Er kann beide Strings festlegen. Dann ist folgender Angriff möglich:

PHP-Code:
<?php

$payload 
= <<<'EOT'
for ($i = 1; $i <= 10; $i++) {
    echo "Code injection *** ";
}
EOT;

$key   'frdl\\\\';
$value '));' $payload 'exit;?>';

// $key und $value sind reguläre Strings, wie ein Nutzer sie irgendwo eingeben könnte.

$foo = new stdClass();
$foo->$key $value;

// JSON erzeugen

$data json_encode($foo);



// Und wieder auswerten

$code var_export(json_decode($data), true);

$code str_replace(array("stdClass::__set_state"'o::__set_state''\o::__set_state''O::__set_state''\O::__set_state'), array("(object)""(object)""(object)""(object)""(object)"), $code);
$code str_replace("\\' => '""\\\\' => '"$code);

eval(
$code); 

Ausgabe unter PHP 5.3.3 (kann wieder mit 3v4l.org nachvollzogen werden, als Preview-Version 5.3.3 einstellen):

Code:
Code injection *** Code injection *** Code injection *** Code injection *** Code injection *** Code injection *** Code injection *** Code injection *** Code injection *** Code injection ***

Es ist also trotz deiner Ersetzungen unter gewissen Bedingungen noch möglich, mit Eingaben aus dem String-Kontext auszubrechen und so (mehr oder weniger) beliebigen PHP-Code in die Anwendung einzuschleusen.

Fairerweise kann man aber wohl sagen, dass das an der Stelle wohl tendenziell nicht an deinen Ersetzungen liegt, sondern an den var_export-Bugs in PHP. Ganz sicher bin ich mir nicht.

(Deine zweite Ersetzung ist „zufällig“ (?) safe (hinsichtlich meiner bisherigen Bemühungen), weil var_export anscheinend nur Single-Quote-Strings erzeugt (?). Darin werden Single-Quotes aus Nutzereingaben dann als \' escapet. Deshalb lässt sich wahrscheinlich keine Nutzereingabe konstruieren, die von der zweiten str_replace-Zeile betroffen wäre. Weil es nicht möglich ist, das alleinstehende und nicht escapete Single-Quote im Suchstring zu erzeugen.)

Aber vielleicht konnte ich etwas verdeutlichen, dass man mit so was echt aufpassen muss.
Antworten
#10
Zitat:Aber vielleicht konnte ich etwas verdeutlichen, dass man mit so was echt aufpassen muss.
Danke das Du Dir die Zeit genommen hast! Cool

Ich habe den str_replace Quatsch entfernt.
Statt die aufbereiteten Ergebnisse cache ich im Moment nur die request-response.

Zitat:Von 5.3.3 kommst du wirklich nicht irgendwie weg?
Langfristig ja, kurzfristig nein:
Auf dem Server sind (quantitativ) sehr umfangreiche und teilweise zusammenhängende "stable" Projekte ("never change running systems") in production mode, da wird ein update nicht an einem Tag getan sein. Ich werde sehen ob ich auf dem Server irgendwie 2 php versionen installieren und nach und nach updaten kann (?).
Zudem habe ich noch genug andere "offene Baustellen" auch mit "neuem" code die ich alsbald "fertig" bekommen möchte.
Zitat:Du musst ja theoretisch nur bis 5.3.7 upgraden. (Mehr wäre natürlich besser, aber da muss man dann schon eher mal schauen, was Kompatibilität angeht.)
Nur für diesen einen Bug mache ich kein Update auf 5.3.7, wenn dann "Nägel mit Köpfen".
Antworten


Gehe zu: