静态分析-RIPS-源码解析记录-03

既然有源码可以debug,那么直接跑测试用例,来跟踪处理逻辑感觉比直接看代码理逻辑更快一些,尤其是涉及到了扫描阶段,不然不容易弄清某刻某个变量的取值。

对于所有漏洞而言,都是由sink点到source点检测是否有过滤函数,那么sink和source之间到底隔了多远,中间函数之间,类之间有何种复杂的联系也决定了当前静态检测算法提供的功能能不能正确实现正确的回溯,对测试用例列举的越详细,就能够提升阅读源码的效率。

搭配简单例子进行扫描分析:

那么对于source和sink,通过其定义位置,首先对其进行一个预先分类,方便写测试用例:

①.sink定义在函数内部不在类里:

sink in function and not in class

②.sink定义在函数外

sink in function

③.sink定义在函数内在类里:

sink in function and in class

那么对于source,此时选择$_GET,暂时不加过滤函数

第一种情况:

此时debug整个过程:

此时遍历$a=$_GET['tr1ple'];时,这种情况涉及到变量的赋值,分为以下几种情况:

1.赋值一个非数组变量

非数组变量又分为:

a.赋值为一个函数调用的返回值

b.一个类的实例

c.普通字符串、int等基本数据类型

2.赋值一个数组变量

数组变量又分为:

a.array(1,2,3,4,5)

b.array("a"=>1,"b"=>2)

即有键和无键两种

函数定义有固定的function标识的token,通过scanner的成员变量in_function来标识此时进入函数内部

此时rips识别出的source即为$_GET[tr1ple]

 此时rips识别出来是代码执行,并且已经能够识别出用户的输入被传递到函数的入口参数中,并且用户的输入作为动态的函数名,即可以导致任意函数调用

 对$a=$_GET[tr1ple]的扫描规则如下:

首先在this->tokens数组中,$a所在的token数组index=0已经标识其为变量,那么接下来在rips中判断如下,进入如下逻辑:

 对于当前为变量又分为以下几种情况:

a.如果下一个token为(,则可能为任意函数调用

b.如果前一个token为左花括号{,并且前前token为$,也就是${$xxx}的形式,那么这种情况可能存在变量覆盖的风险

c.如果前一个是as或者前一个是=>再前一个是变量,再前一个是as,那么就是foreach结构

d.对于在for()循环中的话,只需要关心是否前前token为for,并切下一个token为赋值运算符

e.当前是变量定义

那么在这个例子中即进入了这个if体,这里又回到之前所说,对于变量的定义要区分是数组还是非数组两大类,如果当前定义的是一个数组

那么对于非数组,就是anything,即其它类型的变量了

 

 此时调用variable_add函数,该函数需要:

a.当前的token_value 即$a变量名

b.一个array_slice处理后的数组

其中返回的是当前tokens数组的一个子集,其中调用Analyzer的getBraceEnd函数

那么这个函数就是针对扫描当前被赋值的变量是到哪个token结束,首先定义一个游标c,然后再定义一个$newbraceopen来寻找圆括号,这里实际上对于普通的变量分为上面说过的3种:

(1).函数返回值

即 $a=func();

(2).类实例

$a=new classa();

(3).普通变量:

$a="tr1ple";

那么下面的循环实际上就是去找到;分号,也就是php语句的结束符,这里的找()在这貌似并无太大作用,因为最终返回的游标是落在;分号处的,

所以这里getBraceEnd返回+1就是分号的下一个token,即对于当前被赋值的变量,用vardeclare的end键存储它的变量声明结束的地方,这里实际就是一个游标距离,当前变量的token位置+游标距离就是;分号,所以这里这里要取的token数组包括游标从1开始到游标结束的所有,外层返回后再+1即包括当前变量所代表的token,所以variable_add的第二个变量的值即为要赋值的变量的到赋值结束的token数组,包括结束;分号。

c.comment,这里为空

d.tokenscanstart 也就是判断当前是不是赋值开始,即判断当前变量是否在常见的赋值运算符中

e.tokenscanend token扫描结束

f.line_nr 赋值变量所在的行号

g.$i 当前变量索引

h.当前变量索引为3处是否设置,没设置则初始化为数组

在该函数中,首先要完成一个VarDeclare的初始化过程,这里传入的有变量包含的tokens子集,以及comment,对于该类还要保存这个被赋值变量的行号以及当前被赋值变量的token索引

function variable_add($var_name, $tokens, $comment='', $tokenscanstart, $tokenscanstop, $linenr, $id, $array_keys=array(), $additional_keys=array()){// add variable declaration to beginning of varlist$new_var = new VarDeclare($tokens,$this->comment . $comment);$new_var->line = $linenr; //token行号$new_var->id = $id; //token索引if($tokenscanstart)  $new_var->tokenscanstart = $tokenscanstart;if($tokenscanstop) $new_var->tokenscanstop = $tokenscanstop;// add dependenciesforeach($this->dependencies as $deplinenr=>$dependency) //如果有依赖则为当前变量添加依赖{if(!empty($dependency))$new_var->dependencies[$deplinenr] = $dependency;}// if $GLOBALS['x'] is used outside a function its the same as using var $x, rewriteif($var_name === '$GLOBALS' && !empty($array_keys) && !$this->in_function){$var_name = '$'.array_shift($array_keys);  //对于全局变量的处理,如果当前是global,那么在token重构时已经放到了global的索引为3的数组中,那么此时返回的就是某个全局变量的名字,并且如果此时不在函数内部,则将当前该全局变量赋值给$var_name,也就是起到重新赋值的作用,这么做还是要依靠好之前的对数组重构的过程,因为global也是个数组~}// add additional array keys  //暂时没想到这个是干啥的,不过要是不为空的话,就要和array_keys合并if(!empty($additional_keys)){if(empty($array_keys))$array_keys[] = $additional_keys;else    $array_keys = array_merge($array_keys, array($additional_keys));}// add/resolve array keysif(!empty($array_keys)) //接着解析array_keys,若其不为空{foreach($array_keys as $key){if(!is_array($key)) //key非数组,也就是非变量的键名$new_var->array_keys[] = $key;    //取出其中的每个key,赋值给new_var的array_keyselse{   //对于变量型的键名,这里调用get_tokens_value来拿到该token的value$recstring = Analyzer::get_tokens_value( $this->file_pointer,$key, $this->in_function ? $this->var_declares_local : $this->var_declares_global, $this->var_declares_global, $id);if(!empty($recstring))$new_var->array_keys[] = $recstring;else$new_var->array_keys[] = '*';}    }}                //如果当前变量定义实在函数中            if($this->in_function){if(!isset($this->var_declares_local[$var_name]))  //如果局部变量列表中没有该值$this->var_declares_local[$var_name] = array($new_var); //将当前被赋值变量所代表的VarDeclares放进局部变量列表中elsearray_unshift($this->var_declares_local[$var_name], $new_var);  //否则将VarDeclare放入该变量所代表数组的第一位置处// if variable was put in global scope, save assignments// later they will be pushed to the global var list when function is calledif(in_array($var_name, $this->put_in_global_scope)){if(!isset($this->globals_from_function[$this->function_obj->name][$var_name]))$this->globals_from_function[$this->function_obj->name][$var_name] = array($new_var);elsearray_unshift($this->globals_from_function[$this->function_obj->name][$var_name], $new_var);}                } else{//没有在函数中则说明当前变量是全局变量if(!isset($this->var_declares_global[$var_name]))$this->var_declares_global[$var_name] = array($new_var); //和局部一样,全局没有则放到全局变量列表中elsearray_unshift($this->var_declares_global[$var_name], $new_var);    //有则放到该变量对应数组的第一位置}}

所以综上所述,variable_add要完成的功能即将当前被赋值变量的相关信息,包括tokens数组子集、行号、当前变量token索引等信息放到VarDeclare这个类中,然后再根据当前变量是否在函数内,决定把当前的VarDeclare实例放到当前扫描文件全局Scanner类的局部变量列表中还是全局列表中,比如这里就将$a所关联的VarDeclare最终放到了全局变量列表中

接着回到variable_add的调用处,调用结束后将保存该变量的开始和结束范围

这里要vardeclare减1是因为之前array_slice的时候第三个值+1所以要减1使范围缩小到变量定义的那一行的;分号结束处

接着就是判断当前被赋值的变量是否在Source定义的userinput中,因为当前$a不在userinput中,因此暂时跳过分析

扫描完$a后,等号直接跳过,然后到了$_GET的扫描:

这里直接到$_GET是否在Source::v_userinput的判断中,也就是下面的if判断代码

            // add user input variables to global finding listif( in_array($token_value, Sources::$V_USERINPUT) ) //如果是在userinput中{if(isset($this->tokens[$i][3])) //对于数组型变量,此时第三个里面保存的就是数组的键名{if(!is_array($this->tokens[$i][3][0])) //如果键名不是变量,是常量,则直接将当前变量以及键名保存在全局的user_input中,以及保存行号和文件名$GLOBALS['user_input'][$token_value.'['.$this->tokens[$i][3][0].']'][$this->file_pointer][] = $line_nr;else//否则说明键名是个变量,则调用get_tokens_value取到该变量的值,然后再保存到user_input中$GLOBALS['user_input'][$token_value.'['.Analyzer::get_tokens_value($this->file_pointer,$this->tokens[$i][3][0],$this->in_function ? $this->var_declares_local : $this->var_declares_global,$this->var_declares_global, $i).']'][$this->file_pointer][] = $line_nr;}    else$GLOBALS['user_input'][$token_value][$this->file_pointer][] = $line_nr;    // count found userinput in function for graphs    if($this->in_function) //如果在函数中{$GLOBALS['user_functions_offset'][$this->function_obj->name][5]++;} else{ //如果不在函数中,则另user_function_offset的__main__的索引5加1$GLOBALS['user_functions_offset']['__main__'][5]++;}}

  接着继续扫描到了分号;,因为之前在扫描到$a时,已经在vardeclare中保存了$a的扫描范围,所以这里end即分号所在的索引,因此此时unset掉$vardeclare

 接着扫描到function的定义:


                else if($token_name === T_FUNCTION) #当前是函数标识{if($this->in_function) #是否已经在函数内部{#addError('New function declaration in function declaration of '.$this->function_obj->name.'() found. This is valid PHP syntax but not supported by RIPS now.', array_slice($this->tokens, $i, 10), $this->tokens[$i][2], $this->file_pointer);}    else{$this->in_function++;  #不是则标识此时进入函数内部// the next token is the "function name()"$i++;$function_name = isset($this->tokens[$i][1]) ? $this->tokens[$i][1] : $this->tokens[$i+1][1]; #获取函数名$ref_name = ($this->in_class ? $this->class_name.'::' : '') . $function_name;  #如果在类内部,则拿到类名和函数名// add POP gadgets to infoif(isset($this->info_functions[$function_name])) //info_functions是否存在该函数名{$GLOBALS['info'][] = $ref_name; //存在则将当前该函数的完整路径存在Global的info数组中// add gadget to output$found_line = highlightline(array_slice($this->tokens,$i-1,4),$this->comment, $line_nr, $function_name, false, $function_name); //存储该函数的位置,highlightline用于输出展示$new_find = new InfoTreeNode($found_line); //将该函数的位置存储到InfoTreeNode实例中$new_find->title = "POP gadget $ref_name";  //title就是该函数的完整名$new_find->lines[] = $line_nr; //存储函数开始所在行号$new_find->filename = $this->file_pointer; //存储当前文件名if(isset($GLOBALS['output'][$this->file_name]['gadgets'])) //如果设置了输出当前文件的gadget,则将当前函数的信息保存在treenodes中$GLOBALS['output'][$this->file_name]['gadgets']->treenodes[] = $new_find; //treenodes是VulnBlock的实例else{//new一个VulnBlock的实例,其中包括tif(当前token的循环索引,为啥不用$i,因为$i实际会变化,比如这里$i指向函数名,而$tif指向function标志)$block = new VulnBlock($this->tif.'_'.$this->tokens[$i][2].'_'.basename($this->file_pointer), 'POP gadgets');$block->vuln = true;$block->treenodes[] = $new_find; $GLOBALS['output'][$this->file_name]['gadgets'] = $block;}} $c=3; //这里声明游标来找左花括号,和分号,因为对于函数定义有两种a.在函数定义时变写好函数体b.只声明,不实现,也就是抽象函数此时默认是忽略了函数的入口参数的扫描while($this->tokens[$i+$c] !== '{' && $this->tokens[$i+$c] !== ';'){  $c++;}// abstract functions endedif($this->tokens[$i+$c] === ';')  #如果是扫描到分号,则说明当前是抽象函数,则退出函数体$this->in_function--;// write to user_functions offset list for referencing in output//此时记录该函数所在的文件和开始行号到user_func_offset中$GLOBALS['user_functions_offset'][$ref_name][0] = $this->file_pointer;$GLOBALS['user_functions_offset'][$ref_name][1] = $line_nr-1;// save function as object//将当前函数保存为FunctionDeclare的实例,其中传入的this->dependencytokens就为函数的左花括号或者是函数声明分号之间的所有token数组,作为该函数依赖的token即对于函数的声明块,肯定要把它的入口参数也和它绑定保存$this->function_obj = new FunctionDeclare($this->dependencytokens = array_slice($this->tokens,$i-1,$c+1));$this->function_obj->lines[] = $line_nr; //函数行号$this->function_obj->name = $function_name; //函数名// save all function parameters$this->function_obj->parameters = array();$e=1;// until function test(...) {//  OR// interface test { public function test(...); }//直到扫到{或者;才说明当前函数扫描结束while( $this->tokens[$i+$e] !== '{' && $this->tokens[$i+$e] !== ';' ){    if( is_array($this->tokens[$i + $e]) && $this->tokens[$i + $e][0] === T_VARIABLE ){$this->function_obj->parameters[] = $this->tokens[$i + $e][1];  //存储该函数定义的所有入口参数}$e++;}//因为已经将函数的入口参数放到函数的para变量中了,所以这里跳过对接下来函数入口参数token的扫描// now skip the params from rest of scan,// or function test($a=false, $b=false) will be detected as var declaration$i+=$e-1; // -1, because '{' must be evaluated again}}

接着要对函数体进行扫描:

接着继续扫描左花括号,在此例中也就是函数体,这里对于花括号解析又分为以下几种情况:

a. ) 和{

b.:和{ 即 case x:{

c.case x;{

d.do {

e.else {

f.class bla {  

g.try {

h. catch{

这里就包含了php语法中所有可能的左花括号和前一种token的搭配情况

                  if($this->tokens[$i] === '{' && ($this->tokens[$i-1] === ')' || $this->tokens[$i-1] === ':' || $this->tokens[$i-1] === ';' // case x:{ or case x;{ || (is_array($this->tokens[$i-1])&& ($this->tokens[$i-1][0] === T_DO  // do {|| $this->tokens[$i-1][0] === T_ELSE // else {|| $this->tokens[$i-1][0] === T_STRING // class bla {|| $this->tokens[$i-1][0] === T_TRY // try {|| $this->tokens[$i-1][0] === T_CATCH)) ) ) // catch{{// save brace amount at start of functionif($this->in_function && $this->brace_save_func < 0) //因为当前已经扫过function标志时将in_function置1,并且brace_save_func默认是-1{$this->brace_save_func = $this->braces_open; //将brace_save_func置0}    // save brace amount at start of classif($this->in_class && $this->brace_save_class < 0) //如果是在类中,则将brace_save_class置0{$this->brace_save_class = $this->braces_open;}$this->in_condition = 0;if(empty($e)){                    if(!$this->ignore_requirement){if(!empty($this->dependencytokens) && $this->dependencytokens[0][0] === T_ELSE && $this->dependencytokens[1][0] !== T_IF ) {$this->dependencytokens = $this->last_dependency;$this->dependencytokens[] = array(T_ELSE, 'else', $this->dependencytokens[0][2]);}    } else{$this->ignore_requirement = false;}// add dependency (even push empty dependency on stack, it will get poped again)$this->dependencies[$line_nr] = $this->dependencytokens;    $this->dependencytokens = array();                        } else{unset($e);}$this->braces_open++;}    

 get_token_value的入口参数包括:

(1).filename 当前被扫描的文件指针,也就是文件名

(2).tokens 当前数组键名

(3).当前变量赋值是在函数内部还是在函数外部

(4).当前被赋值的变量的token索引

(5).start 

(6).stop

(7).source_functions 输入源

function get_tokens_value($file_name, $tokens, $var_declares, $var_declares_global, $tokenid, $start=0, $stop=0, $source_functions=array()){$value = '';if(!$stop) $stop = count($tokens); //默认stop赋值为1// check all tokens until instruction endsfor($i=$start; $i<$stop; $i++) //对传入的token进行遍历{if( is_array($tokens[$i]) ){        // trace variables for its valuesif( $tokens[$i][0] === T_VARIABLE || ($tokens[$i][0] === T_STRING && $tokens[$i+1] !== '(' ) ){if(!in_array($tokens[$i][1], Sources::$V_USERINPUT)){                        // constant CONSTANTSif ($tokens[$i][1] === 'DIRECTORY_SEPARATOR')$value .= '/';else if ($tokens[$i][1] === 'PATH_SEPARATOR')    $value .= ';';// global $varname -> global scope, CONSTANTSelse if( (isset($tokens[$i-1]) && is_array($tokens[$i-1]) && $tokens[$i-1][0] === T_GLOBAL) || $tokens[$i][1][0] !== '$' )$value .= self::get_var_value($file_name, $tokens[$i], $var_declares_global, $var_declares_global, $tokenid);// local scopeelse$value .= self::get_var_value($file_name, $tokens[$i], $var_declares, $var_declares_global, $tokenid); } else{if(isset($tokens[$i][3]))$parameter_name = str_replace(array("'",'"'), '', $tokens[$i][3][0]);else$parameter_name = '';// mark userinput for quote analysisif( ($tokens[$i][1] !== '$_SERVER' || (empty($parameter_name) || in_array($parameter_name, Sources::$V_SERVER_PARAMS) || substr($parameter_name,0,5) === 'HTTP_'))&& !((is_array($tokens[$i-1]) && in_array($tokens[$i-1][0], Tokens::$T_CASTS))|| (is_array($tokens[$i+1]) && in_array($tokens[$i+1][0], Tokens::$T_ARITHMETIC))) )$value.='$_USERINPUT';else$value.='1';}}// add strings // except first string of define('var', 'value')else if( $tokens[$i][0] === T_CONSTANT_ENCAPSED_STRING && !($tokens[$i-2][0] === T_STRING && $tokens[$i-2][1] === 'define')){// add string without quotes$value .= substr($tokens[$i][1], 1, -1); }// add directory name dirname(__FILE__)else if( $tokens[$i][0] === T_FILE && ($tokens[$i-2][0] === T_STRING && $tokens[$i-2][1] === 'dirname')){// overwrite value because __FILE__ is absolute// add slash just to be sure$value = dirname($file_name).'/';}// add numberselse if( $tokens[$i][0] === T_LNUMBER || $tokens[$i][0] === T_DNUMBER || $tokens[$i][0] === T_NUM_STRING ){$value .= round($tokens[$i][1]);}else if( $tokens[$i][0] === T_ENCAPSED_AND_WHITESPACE ){$value .= $tokens[$i][1];}// if in foreach($bla as $key=>$value) dont trace $key, $value backelse if( $tokens[$i][0] === T_AS ){break;}// function callselse if($tokens[$i][0] === T_STRING && $tokens[$i+1] === '('){// stop if strings are fetched from database/file (otherwise SQL query will be added)if (in_array($tokens[$i][1], Sources::$F_DATABASE_INPUT) || in_array($tokens[$i][1], Sources::$F_FILE_INPUT) || isset(Info::$F_INTEREST[$tokens[$i][1]])){break;}// add userinput for functions that return userinputelse if(in_array($tokens[$i][1], $source_functions)){$value .= '$_USERINPUT';}}    }}return $value;}

具体扫描php文件的逻辑是在Sanner这个类中,通过parse方法来实现:

 此时开始解析php文件中的token,进行污点分析

1.第一个if(当前token为数组)

此时首先保存当前token的标识,token值以及token所在行号

1.1当前tocken为变量类型

1.1.1如果当前为变量+左花括号则为动态函数调用

此时理论上应该回溯该变量,调用variable_scan方法,传入当前token的索引i,以及偏移量0,当前类型为eval型,以及对应的title(理论上应该输出的漏洞描述信息)

function variable_scan($i, $offset, $category, $title){if(isset($this->scan_functions[$category]))  #如果待扫描的函数类型指定了eval{// build new find                     $new_find = new VulnTreeNode();  # tree code ,就是把当前节点设为一个漏洞树节点,该类是定义在lib/constructor.php中$new_find->name = $category; #存入eval类型$new_find->lines[] = $this->tokens[$i][2]; #当前变量的行数// count sinks$GLOBALS['file_sinks_count'][$this->file_pointer]++;  #当前文件的sink点+1 (最后要输出这个数量,所以用个全局变量保存)if($this->in_function)  #此时判断当前检测的变凉是不是在函数内部定义的,也就是之前有没有扫到function标识,扫到就标识此时该变量在函数内部{$GLOBALS['user_functions_offset'][$this->function_obj->name][6]++;  } else{$GLOBALS['user_functions_offset']['__main__'][6]++;}            // add dependenciesforeach($this->dependencies as $deplinenr=>$dependency)  #找依赖{if(!empty($dependency))$new_find->dependencies[$deplinenr] = $dependency;}// trace back parameters and look for userinput$userinput = $this->scan_parameter($new_find, $new_find, $this->tokens[$i], $this->tokens[$i][3],$i,$this->in_function ? $this->var_declares_local : $this->var_declares_global, $this->var_declares_global, false, array());// add find to output if function call has variable parameters (With userinput)if( $userinput || $GLOBALS['verbosity'] == 4 ) {$new_find->filename = $this->file_pointer;$new_find->value = highlightline(array_slice($this->tokens, $i-$offset, $offset+3+Analyzer::getBraceEnd($this->tokens, $i+2)), $this->comment, $this->tokens[$i][2], $this->tokens[$i][1], false, array(1));        // add to output                                                        $new_find->title = $title;$block = new VulnBlock($this->tif.'_'.$this->tokens[$i][2].'_'.basename($this->file_pointer), getVulnNodeTitle($category), $this->tokens[$i][1]);$block->treenodes[] = $new_find;if($userinput == 1 || $GLOBALS['verbosity'] == 4){$block->vuln = true;increaseVulnCounter($category);}$GLOBALS['output'][$this->file_name][] = $block;if($this->in_function){$this->ignore_securing_function = true;// mark function in class as vulnif($this->in_class){$this->vuln_classes[$this->class_name][] = $this->function_obj->name;}    }// add register_globals implementationif($category === 'extract'){                $this->variable_add('register_globals', array_merge(array_slice($this->tokens, $i-$offset, ($end=$offset+3+Analyzer::getBraceEnd($this->tokens, $i+2))),array(array(T_COMMENT,'// is like ',0),array(T_STRING,'import_request_variables',0),'(',')')), 'see above', 1, $end+2, $this->tokens[$i][2], $i, isset($this->tokens[$i][3]) ? $this->tokens[$i][3] : array());    }}    }    }    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/9913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库管理-第187期 23ai:怎么用SQL创建图(20240510)

数据库管理187期 2024-05-10 数据库管理-第187期 23ai:怎么用SQL创建图&#xff08;20240510&#xff09;1 安装PGX1.1 数据库配置对应用户1.2 使用RPM包安装Graph Server1.3 安装Oracle Graph Client1.4 访问PGX页面 2 SQL Property Graph2.1 创建SQL属性图2.2 关于点和边图元…

副业兼职没那么难,视频号带货,1天稳定500,适合新手操作

向大家推荐一个项目&#xff1a;视频号书单号带货玩法。我已经实践了一段时间&#xff0c;并成功售出了1200多单&#xff0c;赚取了2万多元。这个项目表现相当出色&#xff0c;强烈推荐给大家&#xff01; 周周近财&#xff1a;让网络小白少花冤枉钱&#xff0c;赚取第一桶金 …

[机器学习-03] Scikit-Learn机器学习工具包学习指南:主要功能与用法解析

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

芋道系统springcloud模块启动报错,枚举类不能为空

问题描述&#xff1a; Error starting ApplicationContext. To display the conditions report re-run your application with debug enabled. 2024-05-10 15:50:15.756 | ERROR 9120 | main [TID: N/A] o.s.b.d.LoggingFailureAnalysisReporter | ************************…

Vue创建todolist

电子书 第三章&#xff1a; https://www.dedao.cn/ebook/reader?idV5R16yPmaYOMqGRAv82jkX4KDe175w7xRQ0rbx6pNgznl9VZPLJQyEBodb89mqoO 没有使用VUE CLI创建项目。 创建步骤&#xff1a; 1&#xff0c; 用Vite 创建项目 2&#xff0c; npm run dev 运行程序 参照之前的文…

数据结构与算法学习笔记八-二叉树的顺序存储表示法和实现(C语言)

目录 前言 1.数组和结构体相关的一些知识 1.数组 2.结构体数组 3.递归遍历数组 2.二叉树的顺序存储表示法和实现 1.定义 2.初始化 3.先序遍历二叉树 4.中序遍历二叉树 5.后序遍历二叉树 6.完整代码 前言 二叉树的非递归的表示和实现。 1.数组和结构体相关的一些知…

搭建Harbor仓库

文章目录 Harbor仓库搭建Harbor仓库安装 docker 服务修改配置文件 Harbor仓库 搭建Harbor仓库 下载 Harbor 仓库 安装 docker 服务 # step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 yum-config-m…

QT--5

1> 将网络聊天室重新实现一遍 服务器端 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ser new QTcpServer(this); }Widget::~Widget() {delete ui; }vo…

Aapache Tomcat AJP 文件包含漏洞(CVE-2020-1938)

1 漏洞描述 CVE-2020-1938 是 Apache Tomcat 中的一个严重安全漏洞&#xff0c;该漏洞涉及到 Tomcat 的 AJP&#xff08;Apache JServ Protocol&#xff09;连接器。由于 AJP 协议在处理请求时存在缺陷&#xff0c;攻击者可以利用此漏洞读取服务器上的任意文件&#xff0c;甚至…

【Linux】Linux——Centos7安装Nginx

不需要安装包 1.安装依赖 #查看 C 环境是否安装gcc -v #查看 zlib 是否安装cat /usr/lib64/pkgconfig/zlib.pc #查看 pcre 是否安装pcre-config --version 2.安装C #安装C yum install gcc-c 3.安装pcre yum install -y pcre pcre-devel 4.安装zlib #安装 yum install -y zlib…

该问题未得到解决(仅记录)

https://releases.ubuntu.com/bionic/进入网页下载ubuntu 选择烧录软件将下载的Ubuntu烧录到U盘中 之前用这个U盘烧录过一次&#xff0c;成功了&#xff0c;后来应该是U盘受损或者是什么其他原因使得用这个U盘总是烧录失败

ChatGLM大模型简介

ChatGLM系列是国产大语言模型中性能最好、回答准确率最高的大模型。如果有毕业论文、课题研究的需要&#xff0c;可以关注一下这个大模型。 清华大学和智谱AI的第一代ChatGLM-6B在2023年3月份推出&#xff0c;开源模型推出之后不久就获得了很多的关注和使用。3个月后的2023年6…

快速上手文心一言指令

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

【NPM】Nginx Proxy Manager 一键申请 SSL 证书,自动续期,解决阿里云SSL免费证书每3个月失效问题

文章目录 1、NPM 简介2、实战Step 1&#xff1a;环境搭建 也可以看作者安装笔记 Step 2&#xff1a;创建容器 2.1 在系统任意位置创建一个文件夹&#xff0c;此文档以~/nginx-proxy-manager为例。2.2 创建docker-compose.yaml2.3 启动NPM服务 Step 3&#xff1a;配置反向代理3…

搭建知识库必备:12个开源 Wiki 软件工具盘点

在任何成功的公司中&#xff0c;部门间的知识共享是至关重要的。如果没有一个简单的信息交流方法&#xff0c;团队怎样才能有效合作呢&#xff1f;Wiki软件提供了一种创建、组织及在全公司范围内分享知识的直接方法。但是&#xff0c;哪一种Wiki软件是最佳的选择呢&#xff1f;…

给网络镜像模式下的 WSL2 使用 127.0.0.1代理的方法

网络镜像模式下的WSL2虽然复制了宿主机windows的ip&#xff0c;但是仍然无法访问127.0.0.1的代理。经过调查&#xff0c;发现因为WSL2从应用商店下载而来&#xff0c;所以可能是UWP应用&#xff0c;所以需要用工具解除环回代理限制。

Java入门基础学习笔记13——数据类型

数据类型的分类&#xff1a; 基本数据类型 引用数据类型 基本数据类型&#xff1a;4大类8种类型&#xff1a; 定义整形用int&#xff0c;再大的数用long。 package cn.ensource.variable;public class VariableDemo2 {public static void main(String[] args) {//目标&#x…

Android11 InputDispatcher 分发事件流程分析

在 Android11 InputReader分析 一文中分析到&#xff0c;InputReader将数据放入iq队列后&#xff0c;唤醒InputDispatcher线程&#xff0c;执行InputDispatcher的dispatchOnce方法 //frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp void InputDispa…

【js】将一维数组处理成树形数据并且实现模糊查询

项目中由于数据量不大&#xff0c;后台并未做处理&#xff0c;因此前端拿到返回的Table数据需要处理成树形数据再渲染到表格中 原始数据 const dataList[{"id": 44,"seedlingName": "测试2","seedlingType": "测试2",&quo…

一文读懂开源大数据OLAP

企业需要从海量数据中提取有价值的信息&#xff0c;以支持决策制定和提高运营效率&#xff0c;数据已成为企业最宝贵的资产之一。OLAP&#xff08;在线分析处理&#xff09;技术&#xff0c;作为数据仓库解决方案的核心组成部分&#xff0c;提供了一种强大的工具&#xff0c;帮…