In a proper implementation of delegation you can expose a subset of interfaces on the inner object. You have a bug in your code in the aggregated (inner) class. You should *not* be able to get I2 directly from the inner class I1 via QueryInterface.
Your inner component is supposed to be delegating its QueryInterface, AddRef, and Relase calls to the outer object on all interfaces except IUnknown. That is why the "Create" method for your Coclass takes an IUnknown pointer (pUnkOuter). Your inner class is supposed to be stashing that pointer to the containing component's IUnknown interface to avoid the very problem you are describing. Then, the inner object delegates AddRef, Release, and QueryInterface calls to that stashed away IUnknown interface from the outer object, unless the QI, AddRef, or Release method is called explicity on the inner object's IUnknown interface (which, in the aggregation case, will only be visible to the containing component).
It is (should be) impossible for the client code to get the inner component's IUnknown interface, because when/if the client asks for I1's IUnknown, the inner object delegates to the outer object, which gives ITS own IUnknown. If the client code can get a different IUnknown by querying on I1 (which would be the case for your component, based on the problem description) then your component is breaking one of the fundemental rules of COM, because the IUnknown pointer is supposed to be usable as the identify of a CoClass instance.
When the outer object initiates the aggregation, it should keep its own copy of the inner object's original IUnknown, which it uses later to release the object, or to ask for any of the interfaces needed for the actual aggregation. The outer component can use this saved IUnknown pointer to the inner component in its own QueryInterface implementation, to get the specific interfaces it wants to delegate. The list of delegated interfaces is controlled by the outer object, in the outer object's QI implementation.
Based on what you have described, you may have additional bugs that could lead to overall instability of your system. If you aren't delegating QI to the outer object, then you might also be seeing strange AddRef/Release behaviour based on the components getting confused about who is controlling the refcount. When you fix the inner/outer delegation, these problems will go away too.
A great place for you to get more information on this is the "Aggregation" article on MSDN. It has sample code for a proper manual implementation of the inner class delegation responsibilites.
ATL does this for you automatically, by the way. To a certain extent, you may be reimplementing the wheel in doing this by hand. My guess is that you have been avoiding ATL because of the complexity, but the tradeoff is that you are learning all the details the hard way. If you are going to be going much further with this kind of COM work, I recommend you take another look at ATL.