OPC服务器到底安不安全?

上篇写的是客户端的应用,这次谈谈服务端,服务器的安全性。你要写一个基于COM的OPC服务器,一般要先定义执行的接口见下(摘自OpcDa30Server.idl),

[
    uuid(ED0E3ADE-0CF5-47cd-A1AB-522E5E75D5CD),
    helpstring("OPC Data Access 3.00 Source Server")
]
coclass OpcDa30Server
{
    [default] interface IOPCServer;
    [source]  interface IOPCShutdown;
};

如果你要写一个基于.NET的OPC服务器,一般会采用OPC基金会的1.06版SDK,最泛化的是如下定义(摘自Opc.IServer.cs),

public interface IServer : IDisposable	
{
    event ServerShutdownEventHandler ServerShutdown;
    string GetLocale();
    string SetLocale(string locale);
    string[] GetSupportedLocales();
    string GetErrorText(string locale, ResultID resultID);
}

进一步的具体执行的服务器定义如下(摘自Opc.Da.IServer.cs),

public interface IServer : Opc.IServer
{
    int GetResultFilters();	
    void SetResultFilters(int filters);
    ServerStatus GetStatus();
    ItemValueResult[] Read(Item[] items);
    IdentifiedResult[] Write(ItemValue[] values);
    ISubscription CreateSubscription(SubscriptionState state);
    void CancelSubscription(ISubscription subscription);
    BrowseElement[] Browse(ItemIdentifier itemID, BrowseFilters filters, out BrowsePosition position);
    BrowseElement[] BrowseNext(ref BrowsePosition position);
    ItemPropertyCollection[] GetProperties(ItemIdentifier[] itemIDs,PropertyID[] propertyIDs, bool returnValues);
}

看到这里,看官脑海里可能会问这样定义有什么错吗?哪里不安全呢?

答案是depends。对于一个布置在微软系统下的OPC服务器及使用微软的网络,使用的是微软的域名账号(如corp\user)。如果你的域名帐户没有被攻陷,OPC服务器是安全的,如果攻陷了就哈哈了。大家还记得2015年发生的蜻蜓工控攻击事件么?它就是采用水坑攻击获得了在工控网里使用的域名账号和密码,进而在微软网络下通过遍历找到了OPC服务器,调用OPC的接口获得了相应点的信息,进而可以随意读写。这样的OPC服务器安全吗?当然不是。有没有正解?

其实OPC基金会给出了关于安全方面的接口,但是大家都不执行,觉得已经在使用了微软的网络被攻破的风险很小。相关的COM接口如下(摘自OpcSec.idl),

[
    object,
    uuid(7AA83A02-6C77-11d3-84F9-00008630A38B),
    pointer_default(unique)
]
interface IOPCSecurityPrivate : IUnknown
{
    HRESULT IsAvailablePriv([out] BOOL* pbAvailable);
    HRESULT Logon([in, string] LPCWSTR szUserID, [in, string] LPCWSTR szPassword);
    HRESULT Logoff(void);
};

在C#里的相应接口(摘自Security.cs),

[ComImport]
[GuidAttribute("7AA83A02-6C77-11d3-84F9-00008630A38B")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
public interface IOPCSecurityPrivate
{
        void IsAvailablePriv([Out][MarshalAs(UnmanagedType.I4)] out int Available);
        void Logon([MarshalAs(UnmanagedType.LPWStr)] string szUserID,         
                   [MarshalAs(UnmanagedType.LPWStr)] string szPassword);
        void Logoff();
};

可见OPC基金会预见了可能的安全问题,给出了自己的答案。问题是——你的OPC服务器端执行了吗?显而易见,包括OPC自己的样本服务端程序都没有执行安全接口,更遑论那些以SDK为基础的二次开发商了。

那怎样补救呢?在以上的COM定义的类里加上IOPCSecurityPrivate,

[
    uuid(ED0E3ADE-0CF5-47cd-A1AB-522E5E75D5CD),
    helpstring("OPC Data Access 3.00 Source Server")
]
coclass OpcDa30Server
{
    [default] interface IOPCServer;
    [source]  interface IOPCShutdown;
    interface IOPCSecurityPrivate;
};

当执行IOPCSecurityPrivate下的Logon时基金会给出的样本如下(摘自COpcCommon.cpp)。当然你可以把其中的m_cUserName换成窗口的安全识别(如定义一个DWORD dwUserSecurityID,然后调用窗口相应的身份API来获得返回的dwUserSecurityID)

HRESULT COpcCommon::Logon(LPCWSTR szUserID, LPCWSTR szPassword)
{
	if (szUserID == NULL || szUserID[0] == 0)
	{
		m_cUserName.Empty();
		return S_OK;
	}

	if (szPassword == NULL || szPassword[0] == 0)
	{
		return E_FAIL;
	}

	m_cUserName = szUserID;
	return S_OK;
}

这样当我们执行IOPCServer下的AddGroup()函数时加上对身份的检测(摘自COpcDaServer.cpp),如不符立即返回如下,

HRESULT COpcDaServer::AddGroup(LPCWSTR szName, BOOL bActive,DWORD dwRequestedUpdateRate, OPCHANDLE hClientGroup, LONG* pTimeBias, FLOAT*    pPercentDeadband, DWORD dwLCID, OPCHANDLE* phServerGroup, DWORD*   pRevisedUpdateRate, REFIID riid, LPUNKNOWN* ppUnk)
{
    if (m_cUserName.IsEmpty())
        return;

    COpcLock cLock(*this);

    COpcDaGroup* pGroup = NULL;

类似地在C#里(摘自Opc.Da.Server.cs),加上OpcRcw.Security.IOPCSecurityPrivate和它的接口函数,然后当其它的接口比如AddGroup()被调用时可以做类似的如上处理(如定义一个类似m_cUserName或dwUserSecurityID的变量然后检查它的值,如空立即返回)

public class  Server : Opc.Da.IServer, OpcRcw.Security.IOPCSecurityPrivate
    {
        #region OpcRcw.Security.IOPCSecurityPrivate
        public void IsAvailablePriv([MarshalAs(UnmanagedType.I4), Out] out int pbAvailable) {
            /* 执行自己的逻辑看 m_cUserName 或者 dwUserSecurityID 是否还有效*/
        }

        public void Logon([MarshalAs(UnmanagedType.LPWStr)] string szUserID, [MarshalAs(UnmanagedType.LPWStr)] string szPassword) {
            /* 执行自己的逻辑来获得 m_cUserName 或者 dwUserSecurityID */
        }

        public void Logoff() {
            /* 执行自己的逻辑清除 m_cUserName 或者 dwUserSecurityID*/
        }
        #endregion

 总结一下,如果用户使用的是微软域下的账号,为了在用户账号沦陷下的情况下保证OPC服务器的安全,需要创立独立的供OPC服务器登录使用的账号,而且此账号不作他用。在向OPC服务器发送请求时客户端使用的第一个OPC接口应该是Logon(),然后才是搜寻OPC服务器及后面相应的接口调用。除了如上的OPC服务器本身的安全特性,在配置方面如DCOM上也可以做些安全方面的硬化,在此不再赘述。有些看客会问,我已经有了第三方的OPC服务器运行而且没有使用Logon()接口,这种情况怎么办?这种情况比较复杂,需要借助工业防火墙的帮助。如果在创建实例前没有侦测到该接口下的函数应该把TCP的连接重置以断开进一步的访问。关于OPC包深度解析及在工业防火墙下应用的博文在此,继续前行进入下一篇。看完请留言哦!

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值