【转】IE沙箱拖拽安全策略解析
https://xlab.tencent.com/cn/2015/12/17/ie-sandbox-drop-security-policy/
IE沙箱逃逸是IE浏览器安全研究的一个重要课题,其中有一类漏洞会借助ElevationPolicy设置中的白名单程序的缺陷来完成沙箱逃逸。IE在注册表中有一个和ElevationPolicy类似的名为DragDrop策略设置,这引起了我们的注意。在本文中,笔者将以一个攻击者的视角,尝试各种途径来突破IE沙箱的这一安全策略,通过分析所遇到的障碍,达到对IE沙箱拖拽安全策略进行详细解析的目的。
0x01 IE沙箱的拖拽策略
IE沙箱逃逸技术中有一类是利用ElevationPolicy中的白名单程序的问题去执行任意代码,在注册表中,有一个和ElevationPolicy类似的配置,名为DragDrop,具体注册表路径如下:
HKLM\Software\Microsoft\Internet Explorer\Low Rights\DragDrop
如下图所示:
DragDrop Policy值的含义如下:
0:目标窗口是无效的DropTarget,拒绝;
1:目标窗口是有效的DropTarget,但无法复制内容;
2:弹框询问用户,允许后将内容复制到目标窗口;
3:静默允许拖拽。
在一个干净的Windows 8.1系统上,DragDrop目录下默认有三个程序:iexplore.exe, explorer.exe, notepad.exe,它们的Policy值都是3。当目标程序的Policy值为2时,向目标程序窗口拖拽文件,IE会弹出一个提示框,如下图所示:
0x02 Explorer进程的拖拽问题
在从IE往Explorer上拖拽文件时,虽然DragDrop Policy值设置为了3,IE不会弹框,但是Explorer进程会会弹一个提示框,如下图所示:
然而,当我们从IE中向Explorer侧边栏的树形文件夹结构中拖拽文件时,并不会弹框。这应该是Explorer程序实现上的一个疏漏。进一步设想,如果我们能够在IE沙箱中通过程序模拟鼠标的拖拽操作,那么就能够利用Explorer的这个问题跨越IE沙箱的安全边界。
0x03 不使用鼠标完成OLE拖拽
OLE拖拽是一种通用的文件拖拽方式,它采用了OLE的接口设计方法来实现拖拽功能,使得拖拽的实现通用且模块化。OLE拖拽技术包含三个基本接口:
(1) IDropSource接口:表示拖拽操作的源对象,由源对象实现;
(2) IDropTarget接口:表示拖拽操作的目标对象,由目标对象实现;
(3) IDataObject接口:表示拖拽操作中传输的数据,由源对象实现。
下图描述了一个完整的OLE拖拽操作需要实现的关键组件:
我们要模拟鼠标拖拽,则只需要实现IDropSource和IDataObject接口。正常的OLE拖拽操作的核心是调用ole32!DoDragDrop函数,该函数原型如下:
HRESULT DoDragDrop(
    IDataObject    *pDataObject,   // Pointer to the data object
    IDropSource    *pDropSource,   // Pointer to the source
    DWORD          dwOKEffect,     // Effects allowed by the source
    DWORD          *pdwEffect      // Pointer to effects on the source
);
DoDragDrop的参数中包含了拖拽源对象和拖拽数据的信息,在DoDragDrop函数内部通过鼠标指针位置来获取拖拽目标对象的信息。接下来,笔者给出一种不使用鼠标,而是用代码模拟的方式来完成文件拖拽的方法。要通过代码模拟鼠标拖拽操作,即要将DoDragDrop函数中GUI操作的部分剥离出来,找出真正执行拖拽操作的函数,将所需要的参数直接传递给它来完成拖拽操作。这里以Win7上的ole32.dll 6.1.7601.18915为例,说明DoDragDrop内部的实现。Ole32!DoDragDrop的主要逻辑如下:
HRESULT __stdcall DoDragDrop(
    LPDATAOBJECT pDataObj,
    LPDROPSOURCE pDropSource,
    DWORD        dwOKEffects,
    LPDWORD      pdwEffect)
{
    CDragOperationdrgop;
    HRESULT hr;
    CDragOperation::CDragOperation(
        &drgop,
        pDataObj,
        pDropSource,
        dwOKEffects,
        pdwEffect,
        &hr
    );
    if (hr>= 0)
    {
       while (CDragOperation::UpdateTarget(&drgop)
        && CDragOperation::DragOver(&drgop)
        && CDragOperation::HandleMessages(&drgop))
       hr = CDragOperation::CompleteDrop(&drgop);
    }
    CDragOperation::~CDragOperation(&drgop);
    return hr;
}
CDragOperation::CDragOperation是构造函数,其中重要的初始化操作包括:
ole32!GetMarshalledInterfaceBuffer
ole32!ClipSetCaptureForDrag
    -->ole32!GetPrivateClipboardWindow
ole32!CreateSharedDragFormats
接下来的While循环判断拖拽的状态,最终由CompleteDrop完成拖拽,关键的函数调用如下:
ole32!CDragOperation::UpdateTarget
    -->ole32!CDragOperation::GetDropTarget
    -->ole32!PrivDragDrop
ole32!CDragOperation::DragOver
     -->ole32!CDropTarget::DragOver
        -->ole32!PrivDragDrop
ole32!CDragOperation::CompleteDrop
    -->ole32!CDropTarget::Drop
         -->ole32!PrivDragDrop
可以看到,最终实现拖拽操作的函数是ole32!PrivDragDrop,通过使用函数偏移硬编码函数地址,可以调用到ole32.dll中的内部函数。我们定义了一个DropData函数来模拟鼠标拖拽,输入参数为目标窗口句柄和被拖拽文件的IDataObject指针,主要逻辑如下:
auto DropData(HWND hwndDropTarget, IDataObject* pDataObject)
{
    GetPrivateClipboardWindow(CLIP_CREATEIFNOTTHERE);
    CreateSharedDragFormats(pDataObject);
    void *DOBuffer = nullptr;
    HRESULT result = GetMarshalledInterfaceBuffer(
        IID_IDataObject,
        pDataObject,
        &DOBuffer
    );
    if (SUCCEEDED(result))
    {
        DWORD     dwEffect = 0;
        POINTL    ptl = { 0, 0 };
        void      *hDDInfo = nullptr;
        HRESULT result = PrivDragDrop(
            hwndDropTarget,
            DRAGOP_ENTER,
            DOBuffer,
            pDataObject,
            MK_LBUTTON,
            ptl,
            &dwEffect,
            0,
            &hDDInfo
        );
        if (SUCCEEDED(result))
        {
            HRESULT result = PrivDragDrop(
                hwndDropTarget,
                DRAGOP_OVER,
                0,
                0,
                MK_LBUTTON,
                ptl,
                &dwEffect,
                0,
                &hDDInfo
            );
            if (SUCCEEDED(result))
            {
                HWND hClip = GetPrivateClipboardWindow(CLIP_QUERY);
                HRESULT result = PrivDragDrop(
                    hwndDropTarget,
                    DRAGOP_DROP,
                    DOBuffer,
                    pDataObject,
                    0,
                    ptl,
                    &dwEffect,
                    hClip,
                    &hDDInfo
                );
            }
        }
    }
    return result;
}
目标窗口句柄可以通过FindWindow函数获得,将被拖拽文件封装成一个DataObject并获得其IDataObject接口指针的方法有两种:
(1) 自己编写C++类实现IDataObject接口;
(2) 使用现有类库中的实现,如:MFC, Shell32中均有对拖拽接口实现的相关类。
笔者这里给出使用MFC类库对文件进行封装并获得其IDataObject接口的方法,实现代码如下:
auto GetIDataObjectForFile(CString filePath)
{
    COleDataSource*    pDataSource = new COleDataSource();
    IDataObject*       pDataObject;
    UINT               uBuffSize = 0;
    HGLOBAL            hgDrop;
    DROPFILES*         pDrop;
    TCHAR*             pszBuff;
    FORMATETC          fmtetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    uBuffSize = sizeof(DROPFILES) + sizeof(TCHAR) * (lstrlen(filePath) + 2);
    hgDrop = GlobalAlloc(GHND | GMEM_SHARE, uBuffSize);
    if (hgDrop != nullptr)
    {
        pDrop = (DROPFILES*)GlobalLock(hgDrop);
        if (pDrop != nullptr)
        {
            pDrop->pFiles = sizeof(DROPFILES);
            pszBuff = (TCHAR*)(LPBYTE(pDrop) + sizeof(DROPFILES));
            lstrcpy(pszBuff, (LPCTSTR)filePath);
            GlobalUnlock(hgDrop);
            pDataSource->CacheGlobalData(CF_HDROP, hgDrop, &fmtetc);
            pDataObject = (IDataObject *)pDataSource->GetInterface(&IID_IDataObject);
        }
        else
        {
            GlobalFree(pDrop);
            pDataObject = nullptr;
        }
    }
    else
    {
        GlobalFree(hgDrop);
        pDataObject = nullptr;
    }
    return pDataObject;
}
0x04 IE沙箱的拖拽实现
当我们在IE沙箱中用鼠标进行拖拽操作时,沙箱内的IE Tab进程会通过ShdocvwBroker将数据转发给沙箱外的主进程,在主进程中完成拖拽操作。也就是说,真正完成拖拽操作是在沙箱外的IE主进程内。两个进程的函数调用情况大致如下:
IE子进程(沙箱中):
MSHTML!CDoc::DoDrag
    -->MSHTML!CDragDropManager::DoDrag
        -->combase!ObjectStubless
            -->...发送ALPC消息给IE主进程
IE主进程:
… 接收IE子进程发来的ALPC消息
    -->RPCRT4!Invoke
        -->IEFRAME!CShdocvwBroker::PerformDoDragDrop
            -->IEFRAME!CShdocvwBroker::PerformDoDragDropThreadProc
                -->ole32!DoDragDrop
0x05 IE沙箱对拖拽操作的安全限制
在IE沙箱中,我们是可以直接调到Broker中的函数的。通过自己创建一个IEUserBroker,再由IEUserBroker创建一个ShdocvwBroker,我们就可以调到主进程中的IEFRAME!CShdocvwBroker::PerformDoDragDrop函数。调用的实现方法大致如下:
typedef HRESULT(__stdcall *pCoCreateUserBroker)(IIEUserBroker **ppBroker);
IIEUserBrokerPtrCreateIEUserBroker()
{
    HMODULE hMod = LoadLibrary("iertutil.dll");
    pCoCreateUserBroker CoCreateUserBroker;
    CoCreateUserBroker = (pCoCreateUserBroker)GetProcAddress(hMod, (LPCSTR)58);
    if (CoCreateUserBroker)
    {
        IIEUserBrokerPtr broker;
        HRESULT ret = CoCreateUserBroker(&broker);
        return broker;
    }
    return nullptr;
}
IIEUserBrokerPtr broker = CreateIEUserBroker();
IShdocvwBroker* shdocvw;
broker->BrokerCreateKnownObject(
    CLSID_CShdocvwBroker,
    __uuidof(IShdocvwBroker),
    (IUnknown**)&shdocvw
);
shdocvw->PerformDoDragDrop(
    HWND__ *handle,
    IEDataObjectWrapper *data_obj,
    IEDropSourceWrapper *drop_source,
    ulong a1,
    ulong a2,
    ulong *a3,
    long *a4
);
拖拽功能最终是调用ole32!DoDragDrop函数来实现的,DoDragDrop所需的参数都可以由PerformDoDragDrop函数传入(参考0x03章节中DoDragDrop函数的参数信息)。至此,我们已经可以从沙箱内直接走到沙箱外的ole32!DoDragDrop函数,且传入参数可控。而要模拟鼠标拖拽操作,有两个思路:
(1) 使用0x02章节中所讲的直接调用ole32.dll内部函数的方法;
(2) 调用API改变鼠标位置。
对于第一种方法,由于我们是在沙箱内,只能通过Broker接口的代理才能从沙箱中出来,进入到IE主进程的进程空间。所以我们并不能调到主进程中dll的内部函数,进而这种方法是不可行的。
第二种方法,如果我们能够改变鼠标的位置,那么在ole32!DoDragDrop函数内部通过鼠标位置获取目标窗口信息的步骤就会成功通过,就能够完成模拟鼠标拖拽的目标。然而实验过程中,我们发现在IE沙箱中是无法通过API来改变鼠标指针位置的。下面来具体说明这个问题。
笔者想到的能够改变鼠标指针位置的方法有两种:
(1) 通过SendInput函数模拟鼠标动作。SendInput函数从用户态到内核态的函数调用关系如下所示:
User32!SendInput
    -->user32!NtUserSendInput
        -->win32k.sys!NtUserSendInput
            -->win32k.sys!xxxSendInput
                -->win32k.sys!xxxMouseEventDirect
(2) 通过SetCursorPos函数改变鼠标指针位置。SetCursorPos函数从用户态到内核态的函数调用关系如下:
user32!SetCursorPos
    -->user32!SetPhysicalCursorPos
        -->user32!NtUserCallTwoParam
            -->win32k.sys!NtUserCallTwoParam
                -->win32k.sys!zzzSetCursorPos
                    -->win32k.sys!zzzSetCursorPosByType
先来看SendInput,如果在IE沙箱中直接调用SendInput函数来改变鼠标指针位置的话,会返回0x5拒绝访问错误,这是因为IEShims.dll中对SendInput函数做了hook,在hook函数中做了处理。具体做处理的函数位置如下:
IEShims.dll!NS_InputQueueLowMIC::APIHook_SendInput
    -->IEFRAME!FrameUtilExports::PreSendInput
        -->ShimHelper::PreSendInput
这个hook很容易绕过,我们直接调用NtUserSendInput即可,不过这个函数没有导出,需要通过函数偏移硬编码它的地址。
直接调用NtUserSendInput,该函数不返回错误,但是鼠标指针的位置并没有改变。究其原因,函数调用的失败是由于UIPI(User Interface Privilege Isolation)的限制。调用SetCursorPos函数也会出现相同的情况。
UIPI是从Windows Vista开始系统新加入的一项安全特性,它在Windows内核中实现,具体位置如下:
win32k!CheckAccessForIntegrityLevel
在Win8.1上,这个函数的逻辑如下:
signed int __stdcallCheckAccessForIntegrityLevelEx(
    unsigned   intCurrentProcessIntegrityLevel,
    int        CurrentIsAppContainer,
    unsigned   intTargetProcessIntegrityLevel,
    int        TargetIsAppContainer)
{
    signed int result;
    if (gbEnforceUIPI && CurrentProcessIntegrityLevel < TargetProcessIntegrityLevel)
        result = 0;
    elseif (gbEnforceUIPI && CurrentProcessIntegrityLevel == TargetProcessIntegrityLevel)
        result = CurrentIsAppContainer == TargetIsAppContainer
                 || TargetIsAppContainer == -1
                 || CurrentIsAppContainer == -1
                 || SeIsParentOfChildAppContainer(
                        gSessionId,
                        CurrentIsAppContainer,
                        TargetIsAppContainer
                    );
    else
        result = 1;
    return result;
}
这个函数首先判断源进程和目标进程的Integrity Level,若源IL小于目标IL,则拒绝;若源IL大于目标IL,则允许。接着判断AppContainer属性,若源和目标的IL相等,且均运行在AppContainer中,则判断二者是否满足SeIsParentOfChildAppContainer函数的约束,满足则允许,否则拒绝。
注:ProcessIntegrityLevel和IsAppContainer参数都是从EPROCESS->Win32Process结构中取出来的,这是一个内部结构。SeIsParentOfChildAppContainer是ntoskrnl中的一个内部函数。
0x06 总结
本文详细解析了IE沙箱对于拖拽操作的安全策略,先后分析了IE沙箱的拖拽限制策略、Explorer进程在拖拽限制上存在的问题、ole32.dll实现拖拽的内部原理、IE在沙箱中实现拖拽操作的原理和IE沙箱对拖拽操作进行安全限制的具体位置和实现细节。IE沙箱通过在IEShims.dll中hook特定函数和借助系统的UIPI特性(Windows Vista以上)对拖拽操作进行了有效的安全限制。
参考资料
[1] Understanding and Working in Protected Mode Internet Explorer
https://msdn.microsoft.com/en-us/library/bb250462
[2] OLE Drag and Drop
http://www.catch22.net/tuts/ole-drag-and-drop
[3] How to Implement Drag and Drop between Your Program and Explorer
http://www.codeproject.com/Articles/840/How-to-Implement-Drag-and-Drop-Between-Your-Progra
[4] WINDOWS VISTA UIPI
https://www.coseinc.com/en/index.php?rt=download&act=publication&file=Vista_UIPI.ppt.pdf
【转】IE沙箱拖拽安全策略解析的更多相关文章
- 基于 jq 实现拖拽上传 APK 文件,js解析 APK 信息
		
技术栈 jquery 文件上传:jquery.fileupload,github 文档 apk 文件解析:app-info-parser,github 文档 参考:前端解析ipa.apk安装包信息 - ...
 - 可拖拽GridView代码解析
		
本片学习笔记是对eoe网上一个项目代码的解读.详细项目作者的博客例如以下:http://blog.csdn.net/vipzjyno1/article/details/26514543.项目源代码下载 ...
 - Qt图形视图体系结构示例解析(视图、拖拽、动画)
		
本博的示例来自与QT Example:C:\Qt\Qt5.9.3\Examples\Qt-5.9.3\widgets\graphicsview\dragdroprobot 将通过分析示例完成主要功能: ...
 - CSharpGL(21)用鼠标拾取、拖拽VBO图元内的点、线或本身
		
CSharpGL(21)用鼠标拾取.拖拽VBO图元内的点.线或本身 效果图 以最常见的三角形网格(用GL_TRIANGLES方式进行渲染)为例. 在拾取模式为GeometryType.Point时,你 ...
 - CSharpGL(20)用unProject和Project实现鼠标拖拽图元
		
CSharpGL(20)用unProject和Project实现鼠标拖拽图元 效果图 例如,你可以把Big Dipper这个模型拽成下面这个样子. 配合旋转,还可以继续拖拽成这样. 当然,能拖拽的不只 ...
 - Html5+NodeJS——拖拽多个文件上传到服务器
		
实现多文件拖拽上传的简易Node项目,可以在github上下载,你可以先下载下来:https://github.com/Johnharvy/upLoadFiles/. 解开下载下的zip格式包,建议用 ...
 - dragsort html拖拽排序
		
一.Jquery List DragSort 对于有些页面,如首页的定制,需要进行动态的拖拽排序.由于自己实现比较困难,我们一般会使用一些js插件来实现.dragsort 就是帮助我们完成这一需求.通 ...
 - wpf图片查看器,支持鼠标滚动缩放拖拽
		
最近项目需要,要用到一个图片查看器,类似于windows自带的图片查看器那样,鼠标滚动可以缩放,可以拖拽图片,于是就写了这个简单的图片查看器. 前台代码: <Window x:Class=&qu ...
 - MWeb 1.4 新功能介绍一:引入文件夹到 MWeb 中管理,支持 Octpress、Jekyll 等静态博客拖拽插入图片和实时预览
		
之前在 MWeb 中打开非文档库中的 Markdown 文档,如果文档中有引用到本机图片,是没办法在 MWeb 中显示出来和预览的.这是因为 Apple 规定在 Mac App Store(MAS) ...
 
随机推荐
- js判断输入的input内容是否为数字
			
有时候我们输入的input的内容需要判断一下是否是数字,所以为了更好的客户体验,在前端先处理一下: <input type="text" name="val&quo ...
 - Arthur and Walls CodeForces - 525D (bfs)
			
大意: 给定格点图, 每个'.'的连通块会扩散为矩形, 求最后图案. 一开始想得是直接并查集合并然后差分, 但实际上是不对的, 这个数据就可以hack掉. 3 3 **. .** ... 正解是bfs ...
 - k8s搭建rook-ceph
			
一.介绍 Rook官网:https://rook.io Rook是云原生计算基金会(CNCF)的孵化级项目. Rook是Kubernetes的开源云本地存储协调器,为各种存储解决方案提供平台,框架和支 ...
 - redmine
			
redmine直接复制图片 https://github.com/thorin/redmine_image_clipboard_paste
 - php session 保存到redis 实现session的共享
			
1.redis安装肯定都会了,就不介绍了. 2.核心代码
 - 学号 20175212 《Java程序设计》第九周学习总结
			
学号 20175212 <Java程序设计>第九周学习总结 教材学习内容总结 一.MySQL数据库管理系统 1.在官网上下载并安装MySQL 2.在IDEA中输入测试代码Connectio ...
 - springboot配置视图控制器
			
实现WebMvcConfigurer接口 /** * @descripte 配置自己的视图解析器 */@Configurationpublic class MyViewConfigController ...
 - MSDN订户下载权限被屏蔽的办法
			
使用Chrome浏览器,在加载完成页面之后,按F12,在控制台选项卡当中输入下面代码,即可解除屏蔽. $("#SubMigratedMessageArea").remove(); ...
 - 解读——angeltoken钱包
			
Angeltoken可不可靠,这是每一个会员都会考虑的问题.有风险意识很重要,但是,更重要的是,怎么才能规避风险,最大限度的安全投资呢? AngelToken值得我们每一个想要改变自己处境的平凡人,认 ...
 - APP包打包签名步骤
			
开发混合app上架应用市场,需要进行应用签名,但是申请签名如果没搞过,会特别麻烦,所以我自自己总结了一下申请的步骤,在此记录一下 1.首先需要下载安装java环境即jdk, 2.配置环境变量 假设JD ...