You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止用户篡改Oracle APEX Popup LOV值并确保数据合法入库?

Oracle APEX 20.1 Popup LOV 篡改值校验问题

我使用Oracle APEX 20.1.0.00.13版本,遇到Popup LOV组件的安全校验问题:

  • 创建了禁用手动输入的Popup LOV页面项P9000_MEM_ITEM,采用SQL查询定义LOV:
SELECT 
    MEM_NAME || ' (' || MEM_ID || ')' AS DISPLAY_VALUE,
    MEM_ID AS RETURN_VALUE
FROM 
    MEMORY_IDS
WHERE 
    MEM_STATUS = 'ENABLE'
ORDER BY 
    MEM_ID;
  • 页面渲染后,APEX生成隐藏输入元素存储选中值:
<input type="hidden" name="P9000_MEM_ITEM" id="P9000_MEM_ITEM_HIDDENVALUE" value="ABCDEF">
  • 恶意用户可通过浏览器开发者工具修改隐藏值为无效值(如MEM_STATUS为DISABLED的MEM_ID),APEX会直接将篡改后的值存入数据库,未做LOV校验。

核心挑战:

  1. 问题覆盖应用中1000多个页面的所有Popup LOV;
  2. 部分LOV依赖其他页面项(如MEM_STATUS = 'ENABLE' AND MEM_USER = :P9000_ANOTHER_ITEM),逻辑复杂。

我尝试编写验证查询检查提交值是否合法,但无法动态复用LOV查询,也不清楚APEX如何在运行时动态获取LOV查询并替换页面项引用:

SELECT 
    COUNT(*)
FROM 
    DUAL
WHERE
    :P9000_MEM_ITEM IN (/* Popup LOV Query */);

请问如何防止用户篡改Popup LOV值,确保仅合法、符合LOV查询规则的值存入数据库?


解决方案

1. 利用APEX内置LOV验证(推荐,高复用性)

APEX自带基于LOV的内置验证,无需重复编写SQL:

  • 给目标Popup LOV页面项添加验证,类型选择「值在列表中」
  • 验证源选择「基于列表的验证」,直接关联该页面项对应的LOV(共享组件LOV或页面级LOV均可)
  • 勾选「使用LOV的WHERE子句」,APEX会自动解析LOV中的绑定变量(如:P9000_ANOTHER_ITEM),确保校验逻辑与LOV完全一致

优势:

  • 完全复用现有LOV定义,无需维护重复SQL
  • 自动处理依赖页面项的参数替换
  • 针对1000多个页面,可通过APEX应用导出/批量编辑(或元数据表批量生成)快速部署,避免手动逐个添加

2. 自定义通用PL/SQL验证函数(适合批量处理)

若需更灵活控制,可编写通用PL/SQL函数,通过APEX元数据动态获取LOV查询并执行校验:

FUNCTION validate_popup_lov(p_item_name IN VARCHAR2, p_page_id IN NUMBER DEFAULT apex_application.g_flow_id) RETURN BOOLEAN IS
    l_lov_query VARCHAR2(32767);
    l_return_value VARCHAR2(4000) := apex_util.get_session_state(p_item_name);
    l_count NUMBER;
BEGIN
    -- 获取页面级LOV定义
    SELECT lov_definition
    INTO l_lov_query
    FROM apex_application_page_items
    WHERE application_id = apex_application.g_flow_id
      AND page_id = p_page_id
      AND item_name = p_item_name;

    -- 若为共享组件LOV,切换到共享LOV表查询
    IF l_lov_query LIKE '%SHARED:%' THEN
        SELECT lov_query
        INTO l_lov_query
        FROM apex_application_lovs
        WHERE application_id = apex_application.g_flow_id
          AND lov_name = REPLACE(l_lov_query, 'SHARED:', '');
    END IF;

    -- 提取LOV中的RETURN_VALUE查询部分,添加值校验条件
    l_lov_query := REGEXP_REPLACE(l_lov_query, 'SELECT.*AS DISPLAY_VALUE,', 'SELECT RETURN_VALUE') || ' WHERE RETURN_VALUE = :P_VALUE';
    l_lov_query := REGEXP_REPLACE(l_lov_query, 'ORDER BY.*$', '');

    -- 动态执行校验
    EXECUTE IMMEDIATE l_lov_query INTO l_count USING l_return_value;

    RETURN l_count > 0;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN FALSE;
END;

给每个Popup LOV页面项添加PL/SQL函数体返回布尔值类型的验证,调用该函数:

RETURN validate_popup_lov('P9000_MEM_ITEM', 9000);

注意:

  • 需确保函数有权限访问APEX元数据表(apex_application_page_items、apex_application_lovs)
  • 动态SQL需正确处理绑定变量,避免注入风险
  • 可结合APEX CLI或SQL脚本批量生成所有Popup LOV的验证

3. 数据库层面约束(最后防线)

即使前端和APEX层面做了校验,数据库约束仍能作为最后保障:

  • 针对存储LOV值的字段,添加CHECK约束或外键约束(若值来自固定表)
  • 创建触发器,在插入/更新时校验值是否符合LOV规则

示例触发器(针对MEMORY_IDS表的引用):

CREATE OR REPLACE TRIGGER trg_check_mem_id_valid
BEFORE INSERT OR UPDATE OF mem_id ON your_target_table
FOR EACH ROW
DECLARE
    l_status VARCHAR2(10);
BEGIN
    SELECT mem_status INTO l_status
    FROM memory_ids
    WHERE mem_id = :new.mem_id;

    IF l_status != 'ENABLE' THEN
        RAISE_APPLICATION_ERROR(-20001, '无效的内存ID');
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20002, '内存ID不存在');
END;

该方法可防止绕过APEX校验的非法数据(如直接通过SQL插入)

4. 前端增强校验(辅助手段)

虽无法完全防止篡改,但可增加恶意用户操作成本:

  • 给Popup LOV添加前端事件监听,禁止直接修改隐藏字段
  • 使用APEX的apex.itemAPI监听值变化,确保仅LOV选择的值被提交

页面加载时执行的JavaScript示例:

// 禁止修改Popup LOV的隐藏字段
const hiddenInput = document.getElementById('P9000_MEM_ITEM_HIDDENVALUE');
let lastValidValue = hiddenInput.value;

hiddenInput.addEventListener('change', function() {
    this.value = lastValidValue;
});

// 监听LOV选择事件,更新合法值
apex.item('P9000_MEM_ITEM').on('change', function() {
    lastValidValue = this.getValue();
});

内容的提问来源于stack exchange,提问作者Farshid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:39:58