diff --git a/stix2/base.py b/stix2/base.py
index 2afba16..9b5308f 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -185,7 +185,13 @@ class _STIXBase(collections.Mapping):
 
     # Handle attribute access just like key access
     def __getattr__(self, name):
-        if name in self:
+        # Pickle-proofing: pickle invokes this on uninitialized instances (i.e.
+        # __init__ has not run).  So no "self" attributes are set yet.  The
+        # usual behavior of this method reads an __init__-assigned attribute,
+        # which would cause infinite recursion.  So this check disables all
+        # attribute reads until the instance has been properly initialized.
+        unpickling = "_inner" not in self.__dict__
+        if not unpickling and name in self:
             return self.__getitem__(name)
         raise AttributeError("'%s' object has no attribute '%s'" %
                              (self.__class__.__name__, name))

Infinite recursion on _STIXBase object deserialization
Hi!

There seems to be a bug in the way the `__getitem__`/`__getattr__` functions are implemented in `_STIXBase` (_cti-python-stix2/stix2/base.py_). The bug can be reproduced by trying to serialize/deserialize an object instance, resulting in infinite recursion causing the program to terminate with a `RecursionError` once the max recursion depth is exceeded. 

Here is a short example with copy.copy() triggering the recursion:
```
import copy, stix2
indicator = stix2.Indicator(name="a", labels=["a"], pattern="[file:name = 'a']")
copy.copy(indicator)
```

I believe this is because the unpickling tries to find the optional `__set_state__` method in `_STIXBase`. But since it's not there, the `__getattr__` is being called for it. However, the way `__getattr__` is implemented in the class, the `__getitem__` is called which  tries to look up an item in `self._inner`. Due to the way the deserialization works in general, the object itself is empty and the `__init__` was never called - meaning that the `_inner` attribute was never set. The access attempt on the non-existent `_inner` however triggers a call to `__getattr__` again, resulting in the recursion.

Here are two possible patches, I'm not sure though if that's the proper way of fixing it:

1) Checking for `_inner` access in `__getitem__`:
```
def __getitem__(self, key):
    if key == '_inner':
        raise KeyError
    return self._inner[key]

```
2) Checking for protected attributes in `__getattr__`:
```
def __getattr__(self, name):
    if name.startswith('_'):
        raise AttributeError(name)
    if name in self:
        return self.__getitem__(name)
    raise AttributeError("'%s' object has no attribute '%s'" %
                         (self.__class__.__name__, name))
```

Thanks!
diff --git a/stix2/test/test_pickle.py b/stix2/test/test_pickle.py
new file mode 100644
index 0000000..9e2cc9a
--- /dev/null
+++ b/stix2/test/test_pickle.py
@@ -0,0 +1,17 @@
+import pickle
+
+import stix2
+
+
+def test_pickling():
+    """
+    Ensure a pickle/unpickle cycle works okay.
+    """
+    identity = stix2.Identity(
+        id="identity--d66cb89d-5228-4983-958c-fa84ef75c88c",
+        name="alice",
+        description="this is a pickle test",
+        identity_class="some_class"
+    )
+
+    pickle.loads(pickle.dumps(identity))

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
antlr4-python3-runtime==4.9.3
async-generator==1.10
attrs==22.2.0
Babel==2.11.0
backcall==0.2.0
bleach==4.1.0
bump2version==1.0.1
bumpversion==0.6.0
certifi==2021.5.30
cfgv==3.3.1
charset-normalizer==2.0.12
coverage==6.2
decorator==5.1.1
defusedxml==0.7.1
distlib==0.3.9
docutils==0.18.1
entrypoints==0.4
filelock==3.4.1
identify==2.4.4
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
Jinja2==3.0.3
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
jupyterlab-pygments==0.1.2
MarkupSafe==2.0.1
mistune==0.8.4
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
nbsphinx==0.3.2
nest-asyncio==1.6.0
nodeenv==1.6.0
packaging==21.3
pandocfilters==1.5.1
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
prompt-toolkit==3.0.36
ptyprocess==0.7.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
pyzmq==25.1.2
requests==2.27.1
simplejson==3.20.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.5.6
sphinx-prompt==1.5.0
-e git+https://github.com/oasis-open/cti-python-stix2.git@d67f2da0ea839db464197f7da0f7991bdd774d1f#egg=stix2
stix2-patterns==2.0.0
testpath==0.6.0
toml==0.10.2
tomli==1.2.3
tornado==6.1
tox==3.28.0
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.16.2
wcwidth==0.2.13
webencodings==0.5.1
zipp==3.6.0

name: cti-python-stix2
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - antlr4-python3-runtime==4.9.3
      - async-generator==1.10
      - attrs==22.2.0
      - babel==2.11.0
      - backcall==0.2.0
      - bleach==4.1.0
      - bump2version==1.0.1
      - bumpversion==0.6.0
      - cfgv==3.3.1
      - charset-normalizer==2.0.12
      - coverage==6.2
      - decorator==5.1.1
      - defusedxml==0.7.1
      - distlib==0.3.9
      - docutils==0.18.1
      - entrypoints==0.4
      - filelock==3.4.1
      - identify==2.4.4
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - jupyterlab-pygments==0.1.2
      - markupsafe==2.0.1
      - mistune==0.8.4
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nbsphinx==0.3.2
      - nest-asyncio==1.6.0
      - nodeenv==1.6.0
      - packaging==21.3
      - pandocfilters==1.5.1
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - prompt-toolkit==3.0.36
      - ptyprocess==0.7.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - pyzmq==25.1.2
      - requests==2.27.1
      - simplejson==3.20.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.5.6
      - sphinx-prompt==1.5.0
      - stix2-patterns==2.0.0
      - testpath==0.6.0
      - toml==0.10.2
      - tomli==1.2.3
      - tornado==6.1
      - tox==3.28.0
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.16.2
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - zipp==3.6.0
prefix: /opt/conda/envs/cti-python-stix2

stix2/test/test_pickle.py::test_pickling
stix2/base.py
stix2/base.py